View_pat.cpp 226 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320
  1. /*
  2. * View_pat.cpp
  3. * ------------
  4. * Purpose: Pattern tab, lower panel.
  5. * Notes : Welcome to about 7000 lines of, err, very beautiful code.
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Mptrack.h"
  12. #include "Mainfrm.h"
  13. #include "InputHandler.h"
  14. #include "Childfrm.h"
  15. #include "Moddoc.h"
  16. #include "SampleEditorDialogs.h" // For amplification dialog (which is re-used from sample editor)
  17. #include "Globals.h"
  18. #include "View_pat.h"
  19. #include "Ctrl_pat.h"
  20. #include "PatternFont.h"
  21. #include "PatternFindReplace.h"
  22. #include "PatternFindReplaceDlg.h"
  23. #include "EffectVis.h"
  24. #include "PatternGotoDialog.h"
  25. #include "MIDIMacros.h"
  26. #include "../common/misc_util.h"
  27. #include "../soundlib/MIDIEvents.h"
  28. #include "../soundlib/mod_specifications.h"
  29. #include "../soundlib/plugins/PlugInterface.h"
  30. #include <algorithm>
  31. OPENMPT_NAMESPACE_BEGIN
  32. // Static initializers
  33. ModCommand CViewPattern::m_cmdOld = ModCommand::Empty();
  34. int32 CViewPattern::m_nTransposeAmount = 1;
  35. IMPLEMENT_SERIAL(CViewPattern, CModScrollView, 0)
  36. BEGIN_MESSAGE_MAP(CViewPattern, CModScrollView)
  37. //{{AFX_MSG_MAP(CViewPattern)
  38. ON_WM_ERASEBKGND()
  39. ON_WM_VSCROLL()
  40. ON_WM_SIZE()
  41. ON_WM_MOUSEWHEEL()
  42. ON_WM_XBUTTONUP()
  43. ON_WM_MOUSEMOVE()
  44. ON_WM_LBUTTONDOWN()
  45. ON_WM_LBUTTONDBLCLK()
  46. ON_WM_LBUTTONUP()
  47. ON_WM_RBUTTONDOWN()
  48. ON_WM_SETFOCUS()
  49. ON_WM_KILLFOCUS()
  50. ON_WM_SYSKEYDOWN()
  51. ON_WM_DESTROY()
  52. ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewPattern::OnCustomKeyMsg)
  53. ON_MESSAGE(WM_MOD_MIDIMSG, &CViewPattern::OnMidiMsg)
  54. ON_MESSAGE(WM_MOD_RECORDPARAM, &CViewPattern::OnRecordPlugParamChange)
  55. ON_COMMAND(ID_EDIT_CUT, &CViewPattern::OnEditCut)
  56. ON_COMMAND(ID_EDIT_COPY, &CViewPattern::OnEditCopy)
  57. ON_COMMAND(ID_EDIT_PASTE, &CViewPattern::OnEditPaste)
  58. ON_COMMAND(ID_EDIT_MIXPASTE, &CViewPattern::OnEditMixPaste)
  59. ON_COMMAND(ID_EDIT_MIXPASTE_ITSTYLE,&CViewPattern::OnEditMixPasteITStyle)
  60. ON_COMMAND(ID_EDIT_PASTEFLOOD, &CViewPattern::OnEditPasteFlood)
  61. ON_COMMAND(ID_EDIT_PUSHFORWARDPASTE,&CViewPattern::OnEditPushForwardPaste)
  62. ON_COMMAND(ID_EDIT_SELECT_ALL, &CViewPattern::OnEditSelectAll)
  63. ON_COMMAND(ID_EDIT_SELECTCOLUMN,&CViewPattern::OnEditSelectChannel)
  64. ON_COMMAND(ID_EDIT_SELECTCOLUMN2,&CViewPattern::OnSelectCurrentChannel)
  65. ON_COMMAND(ID_EDIT_FIND, &CViewPattern::OnEditFind)
  66. ON_COMMAND(ID_EDIT_GOTO_MENU, &CViewPattern::OnEditGoto)
  67. ON_COMMAND(ID_EDIT_FINDNEXT, &CViewPattern::OnEditFindNext)
  68. ON_COMMAND(ID_EDIT_RECSELECT, &CViewPattern::OnRecordSelect)
  69. ON_COMMAND(ID_EDIT_SPLITRECSELECT, &CViewPattern::OnSplitRecordSelect)
  70. ON_COMMAND(ID_EDIT_SPLITKEYBOARDSETTINGS, &CViewPattern::SetSplitKeyboardSettings)
  71. ON_COMMAND(ID_EDIT_UNDO, &CViewPattern::OnEditUndo)
  72. ON_COMMAND(ID_EDIT_REDO, &CViewPattern::OnEditRedo)
  73. ON_COMMAND(ID_PATTERN_CHNRESET, &CViewPattern::OnChannelReset)
  74. ON_COMMAND(ID_PATTERN_MUTE, &CViewPattern::OnMuteFromClick)
  75. ON_COMMAND(ID_PATTERN_SOLO, &CViewPattern::OnSoloFromClick)
  76. ON_COMMAND(ID_PATTERN_TRANSITIONMUTE, &CViewPattern::OnTogglePendingMuteFromClick)
  77. ON_COMMAND(ID_PATTERN_TRANSITIONSOLO, &CViewPattern::OnPendingSoloChnFromClick)
  78. ON_COMMAND(ID_PATTERN_TRANSITION_UNMUTEALL, &CViewPattern::OnPendingUnmuteAllChnFromClick)
  79. ON_COMMAND(ID_PATTERN_UNMUTEALL,&CViewPattern::OnUnmuteAll)
  80. ON_COMMAND(ID_PATTERN_SPLIT, &CViewPattern::OnSplitPattern)
  81. ON_COMMAND(ID_NEXTINSTRUMENT, &CViewPattern::OnNextInstrument)
  82. ON_COMMAND(ID_PREVINSTRUMENT, &CViewPattern::OnPrevInstrument)
  83. ON_COMMAND(ID_PATTERN_PLAYROW, &CViewPattern::OnPatternStep)
  84. ON_COMMAND(IDC_PATTERN_RECORD, &CViewPattern::OnPatternRecord)
  85. ON_COMMAND(ID_PATTERN_DELETEROW, &CViewPattern::OnDeleteRow)
  86. ON_COMMAND(ID_PATTERN_DELETEALLROW, &CViewPattern::OnDeleteWholeRow)
  87. ON_COMMAND(ID_PATTERN_DELETEROWGLOBAL, &CViewPattern::OnDeleteRowGlobal)
  88. ON_COMMAND(ID_PATTERN_DELETEALLROWGLOBAL, &CViewPattern::OnDeleteWholeRowGlobal)
  89. ON_COMMAND(ID_PATTERN_INSERTROW, &CViewPattern::OnInsertRow)
  90. ON_COMMAND(ID_PATTERN_INSERTALLROW, &CViewPattern::OnInsertWholeRow)
  91. ON_COMMAND(ID_PATTERN_INSERTROWGLOBAL, &CViewPattern::OnInsertRowGlobal)
  92. ON_COMMAND(ID_PATTERN_INSERTALLROWGLOBAL, &CViewPattern::OnInsertWholeRowGlobal)
  93. ON_COMMAND(ID_RUN_SCRIPT, &CViewPattern::OnRunScript)
  94. ON_COMMAND(ID_TRANSPOSE_UP, &CViewPattern::OnTransposeUp)
  95. ON_COMMAND(ID_TRANSPOSE_DOWN, &CViewPattern::OnTransposeDown)
  96. ON_COMMAND(ID_TRANSPOSE_OCTUP, &CViewPattern::OnTransposeOctUp)
  97. ON_COMMAND(ID_TRANSPOSE_OCTDOWN, &CViewPattern::OnTransposeOctDown)
  98. ON_COMMAND(ID_TRANSPOSE_CUSTOM, &CViewPattern::OnTransposeCustom)
  99. ON_COMMAND(ID_PATTERN_PROPERTIES, &CViewPattern::OnPatternProperties)
  100. ON_COMMAND(ID_PATTERN_INTERPOLATE_VOLUME, &CViewPattern::OnInterpolateVolume)
  101. ON_COMMAND(ID_PATTERN_INTERPOLATE_EFFECT, &CViewPattern::OnInterpolateEffect)
  102. ON_COMMAND(ID_PATTERN_INTERPOLATE_NOTE, &CViewPattern::OnInterpolateNote)
  103. ON_COMMAND(ID_PATTERN_INTERPOLATE_INSTR, &CViewPattern::OnInterpolateInstr)
  104. ON_COMMAND(ID_PATTERN_VISUALIZE_EFFECT, &CViewPattern::OnVisualizeEffect)
  105. ON_COMMAND(ID_GROW_SELECTION, &CViewPattern::OnGrowSelection)
  106. ON_COMMAND(ID_SHRINK_SELECTION, &CViewPattern::OnShrinkSelection)
  107. ON_COMMAND(ID_PATTERN_SETINSTRUMENT, &CViewPattern::OnSetSelInstrument)
  108. ON_COMMAND(ID_PATTERN_ADDCHANNEL_FRONT, &CViewPattern::OnAddChannelFront)
  109. ON_COMMAND(ID_PATTERN_ADDCHANNEL_AFTER, &CViewPattern::OnAddChannelAfter)
  110. ON_COMMAND(ID_PATTERN_RESETCHANNELCOLORS, &CViewPattern::OnResetChannelColors)
  111. ON_COMMAND(ID_PATTERN_TRANSPOSECHANNEL, &CViewPattern::OnTransposeChannel)
  112. ON_COMMAND(ID_PATTERN_DUPLICATECHANNEL, &CViewPattern::OnDuplicateChannel)
  113. ON_COMMAND(ID_PATTERN_REMOVECHANNEL, &CViewPattern::OnRemoveChannel)
  114. ON_COMMAND(ID_PATTERN_REMOVECHANNELDIALOG, &CViewPattern::OnRemoveChannelDialog)
  115. ON_COMMAND(ID_PATTERN_AMPLIFY, &CViewPattern::OnPatternAmplify)
  116. ON_COMMAND(ID_CLEAR_SELECTION, &CViewPattern::OnClearSelectionFromMenu)
  117. ON_COMMAND(ID_SHOWTIMEATROW, &CViewPattern::OnShowTimeAtRow)
  118. ON_COMMAND(ID_PATTERN_EDIT_PCNOTE_PLUGIN, &CViewPattern::OnTogglePCNotePluginEditor)
  119. ON_COMMAND(ID_SETQUANTIZE, &CViewPattern::OnSetQuantize)
  120. ON_COMMAND(ID_LOCK_PATTERN_ROWS, &CViewPattern::OnLockPatternRows)
  121. ON_COMMAND_RANGE(ID_CHANGE_INSTRUMENT, ID_CHANGE_INSTRUMENT+MAX_INSTRUMENTS, &CViewPattern::OnSelectInstrument)
  122. ON_COMMAND_RANGE(ID_CHANGE_PCNOTE_PARAM, ID_CHANGE_PCNOTE_PARAM + ModCommand::maxColumnValue, &CViewPattern::OnSelectPCNoteParam)
  123. ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewPattern::OnUpdateUndo)
  124. ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewPattern::OnUpdateRedo)
  125. ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT+MAX_MIXPLUGINS, &CViewPattern::OnSelectPlugin)
  126. //}}AFX_MSG_MAP
  127. ON_WM_INITMENU()
  128. ON_WM_RBUTTONDBLCLK()
  129. ON_WM_RBUTTONUP()
  130. END_MESSAGE_MAP()
  131. static_assert(ModCommand::maxColumnValue <= 999, "Command range for ID_CHANGE_PCNOTE_PARAM is designed for 999");
  132. const CSoundFile *CViewPattern::GetSoundFile() const { return (GetDocument() != nullptr) ? &GetDocument()->GetSoundFile() : nullptr; };
  133. CSoundFile *CViewPattern::GetSoundFile() { return (GetDocument() != nullptr) ? &GetDocument()->GetSoundFile() : nullptr; };
  134. const ModSequence &CViewPattern::Order() const { return GetSoundFile()->Order(); }
  135. ModSequence &CViewPattern::Order() { return GetSoundFile()->Order(); }
  136. CViewPattern::CViewPattern()
  137. {
  138. EnableActiveAccessibility();
  139. m_Dib.Init(CMainFrame::bmpNotes);
  140. UpdateColors();
  141. m_PCNoteEditMemory = ModCommand::Empty();
  142. m_octaveKeyMemory.fill(NOTE_NONE);
  143. }
  144. CViewPattern::~CViewPattern()
  145. {
  146. m_offScreenBitmap.DeleteObject();
  147. m_offScreenDC.DeleteDC();
  148. }
  149. void CViewPattern::OnInitialUpdate()
  150. {
  151. CModScrollView::OnInitialUpdate();
  152. EnableToolTips();
  153. ChnVUMeters.fill(0);
  154. OldVUMeters.fill(0);
  155. m_previousNote.fill(NOTE_NONE);
  156. m_splitActiveNoteChannel.fill(NOTE_CHANNEL_MAP_INVALID);
  157. m_activeNoteChannel.fill(NOTE_CHANNEL_MAP_INVALID);
  158. m_nPlayPat = PATTERNINDEX_INVALID;
  159. m_nPlayRow = m_nNextPlayRow = 0;
  160. m_nPlayTick = 0;
  161. m_nTicksOnRow = 1;
  162. m_nMidRow = 0;
  163. m_nDragItem = {};
  164. m_bInItemRect = false;
  165. m_bContinueSearch = false;
  166. m_Status = psShowPluginNames;
  167. m_nXScroll = m_nYScroll = 0;
  168. m_nPattern = 0;
  169. m_nSpacing = 0;
  170. m_nAccelChar = 0;
  171. PatternFont::UpdateFont(m_hWnd);
  172. UpdateSizes();
  173. UpdateScrollSize();
  174. SetCurrentPattern(0);
  175. m_fallbackInstrument = 0;
  176. m_nLastPlayedRow = 0;
  177. m_nLastPlayedOrder = ORDERINDEX_INVALID;
  178. m_prevChordNote = NOTE_NONE;
  179. }
  180. bool CViewPattern::SetCurrentPattern(PATTERNINDEX pat, ROWINDEX row)
  181. {
  182. const CSoundFile *pSndFile = GetSoundFile();
  183. if(pSndFile == nullptr)
  184. return false;
  185. if(pat == pSndFile->Order.GetIgnoreIndex() || pat == pSndFile->Order.GetInvalidPatIndex())
  186. return false;
  187. if(m_pEditWnd && m_pEditWnd->IsWindowVisible())
  188. m_pEditWnd->ShowWindow(SW_HIDE);
  189. m_nPattern = pat;
  190. bool updateScroll = false;
  191. if(pSndFile->Patterns.IsValidPat(pat))
  192. {
  193. if(row != ROWINDEX_INVALID && row != GetCurrentRow() && row < pSndFile->Patterns[m_nPattern].GetNumRows())
  194. {
  195. m_Cursor.SetRow(row);
  196. updateScroll = true;
  197. }
  198. if(GetCurrentRow() >= pSndFile->Patterns[m_nPattern].GetNumRows())
  199. {
  200. m_Cursor.SetRow(0);
  201. updateScroll = true;
  202. }
  203. }
  204. SetSelToCursor();
  205. UpdateSizes();
  206. UpdateScrollSize();
  207. UpdateIndicator();
  208. if(m_bWholePatternFitsOnScreen)
  209. SetScrollPos(SB_VERT, 0);
  210. else if(updateScroll)
  211. SetScrollPos(SB_VERT, (int)GetCurrentRow() * GetRowHeight());
  212. UpdateScrollPos();
  213. InvalidatePattern(true, true);
  214. SendCtrlMessage(CTRLMSG_PATTERNCHANGED, m_nPattern);
  215. return true;
  216. }
  217. // This should be used instead of consecutive calls to SetCurrentRow() then SetCurrentColumn().
  218. bool CViewPattern::SetCursorPosition(const PatternCursor &cursor, bool wrap)
  219. {
  220. // Set row, but do not update scroll position yet
  221. // as there is another position update on the way:
  222. SetCurrentRow(cursor.GetRow(), wrap, false);
  223. // Now set column and update scroll position:
  224. SetCurrentColumn(cursor);
  225. return true;
  226. }
  227. ROWINDEX CViewPattern::SetCurrentRow(ROWINDEX row, bool wrap, bool updateHorizontalScrollbar)
  228. {
  229. const CSoundFile *pSndFile = GetSoundFile();
  230. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidIndex(m_nPattern))
  231. return ROWINDEX_INVALID;
  232. if(wrap && pSndFile->Patterns[m_nPattern].GetNumRows())
  233. {
  234. const auto &order = Order();
  235. if((int)row < 0)
  236. {
  237. if(m_Status[psKeyboardDragSelect | psMouseDragSelect])
  238. {
  239. row = 0;
  240. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL)
  241. {
  242. ORDERINDEX curOrder = GetCurrentOrder();
  243. const ORDERINDEX prevOrd = order.GetPreviousOrderIgnoringSkips(curOrder);
  244. if(prevOrd < curOrder && m_nPattern == order[curOrder])
  245. {
  246. const PATTERNINDEX nPrevPat = order[prevOrd];
  247. if((nPrevPat < pSndFile->Patterns.Size()) && (pSndFile->Patterns[nPrevPat].GetNumRows()))
  248. {
  249. SetCurrentOrder(prevOrd);
  250. if(SetCurrentPattern(nPrevPat))
  251. return SetCurrentRow(pSndFile->Patterns[nPrevPat].GetNumRows() + (int)row);
  252. }
  253. }
  254. row = 0;
  255. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)
  256. {
  257. if((int)row < (int)0)
  258. row += pSndFile->Patterns[m_nPattern].GetNumRows();
  259. row %= pSndFile->Patterns[m_nPattern].GetNumRows();
  260. }
  261. } else //row >= 0
  262. if(row >= pSndFile->Patterns[m_nPattern].GetNumRows())
  263. {
  264. if(m_Status[psKeyboardDragSelect | psMouseDragSelect])
  265. {
  266. row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
  267. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL)
  268. {
  269. ORDERINDEX curOrder = GetCurrentOrder();
  270. ORDERINDEX nextOrder = order.GetNextOrderIgnoringSkips(curOrder);
  271. if(nextOrder > curOrder && m_nPattern == order[curOrder])
  272. {
  273. PATTERNINDEX nextPat = order[nextOrder];
  274. if((nextPat < pSndFile->Patterns.Size()) && (pSndFile->Patterns[nextPat].GetNumRows()))
  275. {
  276. const ROWINDEX newRow = row - pSndFile->Patterns[m_nPattern].GetNumRows();
  277. SetCurrentOrder(nextOrder);
  278. if(SetCurrentPattern(nextPat))
  279. return SetCurrentRow(newRow);
  280. }
  281. }
  282. row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
  283. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)
  284. {
  285. row %= pSndFile->Patterns[m_nPattern].GetNumRows();
  286. }
  287. }
  288. }
  289. if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
  290. {
  291. if(static_cast<int>(row) < 0)
  292. row = 0;
  293. if(row >= pSndFile->Patterns[m_nPattern].GetNumRows())
  294. row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
  295. }
  296. if((row >= pSndFile->Patterns[m_nPattern].GetNumRows()) || (!m_szCell.cy))
  297. return false;
  298. // Fix: If cursor isn't on screen move both scrollbars to make it visible
  299. InvalidateRow();
  300. m_Cursor.SetRow(row);
  301. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  302. UpdateScrollbarPositions(updateHorizontalScrollbar);
  303. InvalidateRow();
  304. PatternCursor selStart(m_Cursor);
  305. if(m_Status[psKeyboardDragSelect | psMouseDragSelect] && !m_Status[psDragnDropEdit])
  306. {
  307. selStart.Set(m_StartSel);
  308. }
  309. SetCurSel(selStart, m_Cursor);
  310. return row;
  311. }
  312. bool CViewPattern::SetCurrentColumn(CHANNELINDEX channel, PatternCursor::Columns column)
  313. {
  314. const CSoundFile *pSndFile = GetSoundFile();
  315. if(pSndFile == nullptr)
  316. {
  317. return false;
  318. }
  319. LimitMax(column, m_nDetailLevel);
  320. m_Cursor.SetColumn(channel, column);
  321. PatternCursor selStart(m_Cursor);
  322. if(m_Status[psKeyboardDragSelect | psMouseDragSelect] && !m_Status[psDragnDropEdit])
  323. {
  324. selStart = m_StartSel;
  325. }
  326. SetCurSel(selStart, m_Cursor);
  327. // Fix: If cursor isn't on screen move both scrollbars to make it visible
  328. UpdateScrollbarPositions();
  329. return true;
  330. }
  331. // Set document as modified and optionally update all pattern views.
  332. void CViewPattern::SetModified(bool updateAllViews)
  333. {
  334. CModDoc *pModDoc = GetDocument();
  335. if(pModDoc != nullptr)
  336. {
  337. pModDoc->SetModified();
  338. pModDoc->UpdateAllViews(this, PatternHint(m_nPattern).Data(), updateAllViews ? nullptr : this);
  339. }
  340. CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this);
  341. }
  342. // Fix: If cursor isn't on screen move scrollbars to make it visible
  343. // Fix: save pattern scrollbar position when switching to other tab
  344. // Assume that m_nRow and m_dwCursor are valid
  345. // When we switching to other tab the CViewPattern object is deleted
  346. // and when switching back new one is created
  347. bool CViewPattern::UpdateScrollbarPositions(bool updateHorizontalScrollbar)
  348. {
  349. // HACK - after new CViewPattern object created SetCurrentRow() and SetCurrentColumn() are called -
  350. // just skip first two calls of UpdateScrollbarPositions() if pModDoc->GetOldPatternScrollbarsPos() is valid
  351. CModDoc *pModDoc = GetDocument();
  352. if(pModDoc)
  353. {
  354. CSize scroll = pModDoc->GetOldPatternScrollbarsPos();
  355. if(scroll.cx >= 0)
  356. {
  357. OnScrollBy(scroll);
  358. scroll.cx = -1;
  359. pModDoc->SetOldPatternScrollbarsPos(scroll);
  360. return true;
  361. } else if(scroll.cx >= -1)
  362. {
  363. scroll.cx = -2;
  364. pModDoc->SetOldPatternScrollbarsPos(scroll);
  365. return true;
  366. }
  367. }
  368. CSize scroll(0, 0);
  369. UINT row = GetCurrentRow();
  370. UINT yofs = GetYScrollPos();
  371. CRect rect;
  372. GetClientRect(&rect);
  373. rect.top += m_szHeader.cy;
  374. int numrows = (rect.bottom - rect.top - 1) / m_szCell.cy;
  375. if(numrows < 1)
  376. numrows = 1;
  377. if(m_nMidRow)
  378. {
  379. if(row != yofs)
  380. {
  381. scroll.cy = (int)(row - yofs) * m_szCell.cy;
  382. }
  383. } else
  384. {
  385. if(row < yofs)
  386. {
  387. scroll.cy = (int)(row - yofs) * m_szCell.cy;
  388. } else if(row > yofs + (UINT)numrows - 1)
  389. {
  390. scroll.cy = (int)(row - yofs - numrows + 1) * m_szCell.cy;
  391. }
  392. }
  393. if(updateHorizontalScrollbar)
  394. {
  395. UINT xofs = GetXScrollPos();
  396. const CHANNELINDEX nchn = GetCurrentChannel();
  397. if(nchn < xofs)
  398. {
  399. scroll.cx = (int)(xofs - nchn) * m_szCell.cx;
  400. scroll.cx *= -1;
  401. } else if(nchn > xofs)
  402. {
  403. int maxcol = (rect.right - m_szHeader.cx) / m_szCell.cx;
  404. if((nchn >= (xofs + maxcol)) && (maxcol >= 0))
  405. {
  406. scroll.cx = (int)(nchn - xofs - maxcol + 1) * m_szCell.cx;
  407. }
  408. }
  409. }
  410. if(scroll.cx != 0 || scroll.cy != 0)
  411. {
  412. OnScrollBy(scroll, TRUE);
  413. }
  414. return true;
  415. }
  416. DragItem CViewPattern::GetDragItem(CPoint point, RECT &outRect) const
  417. {
  418. const CSoundFile *pSndFile = GetSoundFile();
  419. if(pSndFile == nullptr)
  420. return {};
  421. CRect rcClient, rect, plugRect;
  422. GetClientRect(&rcClient);
  423. rect.SetRect(m_szHeader.cx, 0, m_szHeader.cx + GetChannelWidth(), m_szHeader.cy);
  424. plugRect.SetRect(m_szHeader.cx, m_szHeader.cy - m_szPluginHeader.cy, m_szHeader.cx + GetChannelWidth(), m_szHeader.cy);
  425. const auto xOffset = static_cast<CHANNELINDEX>(GetXScrollPos());
  426. const CHANNELINDEX numChannels = pSndFile->GetNumChannels();
  427. // Checking channel headers
  428. if(m_Status[psShowPluginNames])
  429. {
  430. for(CHANNELINDEX n = xOffset; n < numChannels; n++)
  431. {
  432. if(plugRect.PtInRect(point))
  433. {
  434. outRect = plugRect;
  435. return {DragItem::PluginName, n};
  436. }
  437. plugRect.OffsetRect(GetChannelWidth(), 0);
  438. }
  439. }
  440. for(CHANNELINDEX n = xOffset; n < numChannels; n++)
  441. {
  442. if(rect.PtInRect(point))
  443. {
  444. outRect = rect;
  445. return {DragItem::ChannelHeader, n};
  446. }
  447. rect.OffsetRect(GetChannelWidth(), 0);
  448. }
  449. if(pSndFile->Patterns.IsValidPat(m_nPattern) && (pSndFile->GetType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT)))
  450. {
  451. // Clicking on upper-left corner with pattern number (for pattern properties)
  452. rect.SetRect(0, 0, m_szHeader.cx, m_szHeader.cy);
  453. if(rect.PtInRect(point))
  454. {
  455. outRect = rect;
  456. return {DragItem::PatternHeader, 0};
  457. }
  458. }
  459. return {};
  460. }
  461. // Drag a selection to position "cursor".
  462. // If scrollHorizontal is true, the point's channel is ensured to be visible.
  463. // Likewise, if scrollVertical is true, the point's row is ensured to be visible.
  464. // If noMode if specified, the original selection points are not altered.
  465. bool CViewPattern::DragToSel(const PatternCursor &cursor, bool scrollHorizontal, bool scrollVertical, bool noMove)
  466. {
  467. const CSoundFile *pSndFile = GetSoundFile();
  468. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  469. return false;
  470. CRect rect;
  471. int yofs = GetYScrollPos(), xofs = GetXScrollPos();
  472. int row, col;
  473. if(!m_szCell.cy)
  474. return false;
  475. GetClientRect(&rect);
  476. if(!noMove)
  477. SetCurSel(m_StartSel, cursor);
  478. if(!scrollHorizontal && !scrollVertical)
  479. return true;
  480. // Scroll to row
  481. row = cursor.GetRow();
  482. if(scrollVertical && row < (int)pSndFile->Patterns[m_nPattern].GetNumRows())
  483. {
  484. row += m_nMidRow;
  485. rect.top += m_szHeader.cy;
  486. int numrows = (rect.bottom - rect.top - 1) / m_szCell.cy;
  487. if(numrows < 1)
  488. numrows = 1;
  489. if(row < yofs)
  490. {
  491. CSize sz;
  492. sz.cx = 0;
  493. sz.cy = (int)(row - yofs) * m_szCell.cy;
  494. OnScrollBy(sz, TRUE);
  495. } else if(row > yofs + numrows - 1)
  496. {
  497. CSize sz;
  498. sz.cx = 0;
  499. sz.cy = (int)(row - yofs - numrows + 1) * m_szCell.cy;
  500. OnScrollBy(sz, TRUE);
  501. }
  502. }
  503. // Scroll to column
  504. col = cursor.GetChannel();
  505. if(scrollHorizontal && col < (int)pSndFile->GetNumChannels())
  506. {
  507. int maxcol = (rect.right - m_szHeader.cx) - 4;
  508. maxcol -= GetColumnOffset(cursor.GetColumnType());
  509. maxcol /= GetChannelWidth();
  510. if(col < xofs)
  511. {
  512. CSize size(-m_szCell.cx, 0);
  513. if(!noMove)
  514. size.cx = (col - xofs) * (int)m_szCell.cx;
  515. OnScrollBy(size, TRUE);
  516. } else if((col > xofs + maxcol) && (maxcol > 0))
  517. {
  518. CSize size(m_szCell.cx, 0);
  519. if(!noMove)
  520. size.cx = (col - maxcol + 1) * (int)m_szCell.cx;
  521. OnScrollBy(size, TRUE);
  522. }
  523. }
  524. UpdateWindow();
  525. return true;
  526. }
  527. bool CViewPattern::SetPlayCursor(PATTERNINDEX pat, ROWINDEX row, uint32 tick)
  528. {
  529. PATTERNINDEX oldPat = m_nPlayPat;
  530. ROWINDEX oldRow = m_nPlayRow;
  531. uint32 oldTick = m_nPlayTick;
  532. m_nPlayPat = pat;
  533. m_nPlayRow = row;
  534. m_nPlayTick = tick;
  535. if(m_nPlayTick != oldTick && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SMOOTHSCROLL))
  536. InvalidatePattern(true, true);
  537. else if(oldPat == m_nPattern)
  538. InvalidateRow(oldRow);
  539. else if(m_nPlayPat == m_nPattern)
  540. InvalidateRow(m_nPlayRow);
  541. return true;
  542. }
  543. UINT CViewPattern::GetCurrentInstrument() const
  544. {
  545. return static_cast<UINT>(SendCtrlMessage(CTRLMSG_GETCURRENTINSTRUMENT));
  546. }
  547. bool CViewPattern::ShowEditWindow()
  548. {
  549. if(!m_pEditWnd)
  550. {
  551. m_pEditWnd = new CEditCommand(*GetSoundFile());
  552. }
  553. if(m_pEditWnd)
  554. {
  555. m_pEditWnd->ShowEditWindow(m_nPattern, m_Cursor, this);
  556. return true;
  557. }
  558. return false;
  559. }
  560. bool CViewPattern::PrepareUndo(const PatternCursor &beginSel, const PatternCursor &endSel, const char *description)
  561. {
  562. CModDoc *pModDoc = GetDocument();
  563. const CHANNELINDEX chnBegin = beginSel.GetChannel(), chnEnd = endSel.GetChannel();
  564. const ROWINDEX rowBegin = beginSel.GetRow(), rowEnd = endSel.GetRow();
  565. if((chnEnd < chnBegin) || (rowEnd < rowBegin) || pModDoc == nullptr)
  566. return false;
  567. return pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, chnBegin, rowBegin, chnEnd - chnBegin + 1, rowEnd - rowBegin + 1, description);
  568. }
  569. BOOL CViewPattern::PreTranslateMessage(MSG *pMsg)
  570. {
  571. if(pMsg)
  572. {
  573. //We handle keypresses before Windows has a chance to handle them (for alt etc..)
  574. if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) ||
  575. (pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN))
  576. {
  577. CInputHandler *ih = CMainFrame::GetInputHandler();
  578. //Translate message manually
  579. UINT nChar = static_cast<UINT>(pMsg->wParam);
  580. UINT nRepCnt = LOWORD(pMsg->lParam);
  581. UINT nFlags = HIWORD(pMsg->lParam);
  582. KeyEventType kT = ih->GetKeyEventType(nFlags);
  583. InputTargetContext ctx = (InputTargetContext)(kCtxViewPatterns + 1 + m_Cursor.GetColumnType());
  584. // If editing is disabled, preview notes no matter which column we are in
  585. if(!IsEditingEnabled() && TrackerSettings::Instance().patternNoEditPopup)
  586. ctx = kCtxViewPatternsNote;
  587. if(ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull)
  588. {
  589. return true; // Mapped to a command, no need to pass message on.
  590. }
  591. //HACK: fold kCtxViewPatternsFX and kCtxViewPatternsFXparam so that all commands of 1 are active in the other
  592. else
  593. {
  594. if(ctx == kCtxViewPatternsFX)
  595. {
  596. if(ih->KeyEvent(kCtxViewPatternsFXparam, nChar, nRepCnt, nFlags, kT) != kcNull)
  597. return true; // Mapped to a command, no need to pass message on.
  598. } else if(ctx == kCtxViewPatternsFXparam)
  599. {
  600. if(ih->KeyEvent(kCtxViewPatternsFX, nChar, nRepCnt, nFlags, kT) != kcNull)
  601. return true; // Mapped to a command, no need to pass message on.
  602. } else if(ctx == kCtxViewPatternsIns)
  603. {
  604. // Do the same with instrument->note column
  605. if(ih->KeyEvent(kCtxViewPatternsNote, nChar, nRepCnt, nFlags, kT) != kcNull)
  606. return true; // Mapped to a command, no need to pass message on.
  607. }
  608. }
  609. //end HACK.
  610. // Handle Application (menu) key
  611. if(pMsg->message == WM_KEYDOWN && nChar == VK_APPS)
  612. {
  613. OnRButtonDown(0, GetPointFromPosition(m_Cursor));
  614. }
  615. } else if(pMsg->message == WM_MBUTTONDOWN)
  616. {
  617. // Open quick channel properties dialog if we're middle-clicking a channel header.
  618. CPoint point(GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam));
  619. if(point.y < m_szHeader.cy - m_szPluginHeader.cy)
  620. {
  621. PatternCursor cursor = GetPositionFromPoint(point);
  622. if(cursor.GetChannel() < GetDocument()->GetNumChannels())
  623. {
  624. ClientToScreen(&point);
  625. m_quickChannelProperties.Show(GetDocument(), cursor.GetChannel(), point);
  626. return true;
  627. }
  628. }
  629. }
  630. }
  631. return CModScrollView::PreTranslateMessage(pMsg);
  632. }
  633. ////////////////////////////////////////////////////////////////////////
  634. // CViewPattern message handlers
  635. void CViewPattern::OnDestroy()
  636. {
  637. // Fix: save pattern scrollbar position when switching to other tab
  638. // When we switching to other tab the CViewPattern object is deleted
  639. CModDoc *pModDoc = GetDocument();
  640. if(pModDoc)
  641. {
  642. pModDoc->SetOldPatternScrollbarsPos(CSize(m_nXScroll * m_szCell.cx, m_nYScroll * m_szCell.cy));
  643. }
  644. if(m_pEffectVis)
  645. {
  646. m_pEffectVis->DoClose();
  647. m_pEffectVis = nullptr;
  648. }
  649. if(m_pEditWnd)
  650. {
  651. m_pEditWnd->DestroyWindow();
  652. delete m_pEditWnd;
  653. m_pEditWnd = NULL;
  654. }
  655. CModScrollView::OnDestroy();
  656. }
  657. void CViewPattern::OnSetFocus(CWnd *pOldWnd)
  658. {
  659. CScrollView::OnSetFocus(pOldWnd);
  660. m_Status.set(psFocussed);
  661. InvalidateRow();
  662. CModDoc *pModDoc = GetDocument();
  663. if(pModDoc)
  664. {
  665. pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
  666. pModDoc->SetFollowWnd(m_hWnd);
  667. UpdateIndicator();
  668. }
  669. }
  670. void CViewPattern::OnKillFocus(CWnd *pNewWnd)
  671. {
  672. CScrollView::OnKillFocus(pNewWnd);
  673. m_Status.reset(psKeyboardDragSelect | psCtrlDragSelect | psFocussed);
  674. InvalidateRow();
  675. }
  676. void CViewPattern::OnGrowSelection()
  677. {
  678. CSoundFile *pSndFile = GetSoundFile();
  679. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  680. {
  681. return;
  682. }
  683. BeginWaitCursor();
  684. m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
  685. const PatternCursor startSel = m_Selection.GetUpperLeft();
  686. const PatternCursor endSel = m_Selection.GetLowerRight();
  687. PrepareUndo(startSel, PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows(), endSel), "Grow Selection");
  688. const ROWINDEX finalDest = m_Selection.GetStartRow() + (m_Selection.GetNumRows() - 1) * 2;
  689. for(int row = finalDest; row > (int)startSel.GetRow(); row -= 2)
  690. {
  691. if(ROWINDEX(row) >= pSndFile->Patterns[m_nPattern].GetNumRows())
  692. {
  693. continue;
  694. }
  695. int offset = row - startSel.GetRow();
  696. for(CHANNELINDEX chn = startSel.GetChannel(); chn <= endSel.GetChannel(); chn++)
  697. {
  698. for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
  699. {
  700. PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
  701. if(!m_Selection.ContainsHorizontal(cell))
  702. {
  703. // We might have to skip the first / last few entries.
  704. continue;
  705. }
  706. ModCommand *dest = pSndFile->Patterns[m_nPattern].GetpModCommand(row, chn);
  707. ModCommand *src = pSndFile->Patterns[m_nPattern].GetpModCommand(row - offset / 2, chn);
  708. ModCommand *blank = pSndFile->Patterns[m_nPattern].GetpModCommand(row - 1, chn); // Row "in between"
  709. switch(i)
  710. {
  711. case PatternCursor::noteColumn:
  712. dest->note = src->note;
  713. blank->note = NOTE_NONE;
  714. break;
  715. case PatternCursor::instrColumn:
  716. dest->instr = src->instr;
  717. blank->instr = 0;
  718. break;
  719. case PatternCursor::volumeColumn:
  720. dest->volcmd = src->volcmd;
  721. blank->volcmd = VOLCMD_NONE;
  722. dest->vol = src->vol;
  723. blank->vol = 0;
  724. break;
  725. case PatternCursor::effectColumn:
  726. dest->command = src->command;
  727. blank->command = CMD_NONE;
  728. break;
  729. case PatternCursor::paramColumn:
  730. dest->param = src->param;
  731. blank->param = 0;
  732. break;
  733. }
  734. }
  735. }
  736. }
  737. // Adjust selection
  738. m_Selection = PatternRect(startSel, PatternCursor(std::min(finalDest, static_cast<ROWINDEX>(pSndFile->Patterns[m_nPattern].GetNumRows() - 1)), endSel));
  739. InvalidatePattern();
  740. SetModified();
  741. EndWaitCursor();
  742. SetFocus();
  743. }
  744. void CViewPattern::OnShrinkSelection()
  745. {
  746. CSoundFile *pSndFile = GetSoundFile();
  747. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  748. {
  749. return;
  750. }
  751. BeginWaitCursor();
  752. m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
  753. const PatternCursor startSel = m_Selection.GetUpperLeft();
  754. const PatternCursor endSel = m_Selection.GetLowerRight();
  755. PrepareUndo(startSel, endSel, "Shrink Selection");
  756. const ROWINDEX finalDest = m_Selection.GetStartRow() + (m_Selection.GetNumRows() - 1) / 2;
  757. for(ROWINDEX row = startSel.GetRow(); row <= endSel.GetRow(); row++)
  758. {
  759. const ROWINDEX offset = row - startSel.GetRow();
  760. const ROWINDEX srcRow = startSel.GetRow() + (offset * 2);
  761. for(CHANNELINDEX chn = startSel.GetChannel(); chn <= endSel.GetChannel(); chn++)
  762. {
  763. ModCommand *dest = pSndFile->Patterns[m_nPattern].GetpModCommand(row, chn);
  764. ModCommand src;
  765. if(row <= finalDest)
  766. {
  767. // Normal shrink operation
  768. src = *pSndFile->Patterns[m_nPattern].GetpModCommand(srcRow, chn);
  769. // If source command is empty, try next source row (so we don't lose all the stuff that's on odd rows).
  770. if(srcRow < pSndFile->Patterns[m_nPattern].GetNumRows() - 1)
  771. {
  772. const ModCommand &srcNext = *pSndFile->Patterns[m_nPattern].GetpModCommand(srcRow + 1, chn);
  773. if(src.note == NOTE_NONE)
  774. src.note = srcNext.note;
  775. if(src.instr == 0)
  776. src.instr = srcNext.instr;
  777. if(src.volcmd == VOLCMD_NONE)
  778. {
  779. src.volcmd = srcNext.volcmd;
  780. src.vol = srcNext.vol;
  781. }
  782. if(src.command == CMD_NONE)
  783. {
  784. src.command = srcNext.command;
  785. src.param = srcNext.param;
  786. }
  787. }
  788. } else
  789. {
  790. // Clean up rows that are now supposed to be empty.
  791. src = ModCommand::Empty();
  792. }
  793. for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
  794. {
  795. PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
  796. if(!m_Selection.ContainsHorizontal(cell))
  797. {
  798. // We might have to skip the first / last few entries.
  799. continue;
  800. }
  801. switch(i)
  802. {
  803. case PatternCursor::noteColumn:
  804. dest->note = src.note;
  805. break;
  806. case PatternCursor::instrColumn:
  807. dest->instr = src.instr;
  808. break;
  809. case PatternCursor::volumeColumn:
  810. dest->vol = src.vol;
  811. dest->volcmd = src.volcmd;
  812. break;
  813. case PatternCursor::effectColumn:
  814. dest->command = src.command;
  815. break;
  816. case PatternCursor::paramColumn:
  817. dest->param = src.param;
  818. break;
  819. }
  820. }
  821. }
  822. }
  823. // Adjust selection
  824. m_Selection = PatternRect(startSel, PatternCursor(std::min(finalDest, static_cast<ROWINDEX>(pSndFile->Patterns[m_nPattern].GetNumRows() - 1)), endSel));
  825. InvalidatePattern();
  826. SetModified();
  827. EndWaitCursor();
  828. SetFocus();
  829. }
  830. void CViewPattern::OnClearSelectionFromMenu()
  831. {
  832. OnClearSelection();
  833. }
  834. void CViewPattern::OnClearSelection(bool ITStyle, RowMask rm) //Default RowMask: all elements enabled
  835. {
  836. CSoundFile *pSndFile = GetSoundFile();
  837. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled_bmsg())
  838. {
  839. return;
  840. }
  841. BeginWaitCursor();
  842. // If selection ends to a note column, in ITStyle extending it to instrument column since the instrument data is
  843. // removed with note data.
  844. if(ITStyle && m_Selection.GetEndColumn() == PatternCursor::noteColumn)
  845. {
  846. PatternCursor lower(m_Selection.GetLowerRight());
  847. lower.Move(0, 0, 1);
  848. m_Selection = PatternRect(m_Selection.GetUpperLeft(), lower);
  849. }
  850. m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
  851. PrepareUndo(m_Selection, "Clear Selection");
  852. ApplyToSelection([&] (ModCommand &m, ROWINDEX row, CHANNELINDEX chn)
  853. {
  854. for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
  855. {
  856. PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
  857. if(!m_Selection.ContainsHorizontal(cell))
  858. {
  859. // We might have to skip the first / last few entries.
  860. continue;
  861. }
  862. switch(i)
  863. {
  864. case PatternCursor::noteColumn: // Clear note
  865. if(rm.note)
  866. {
  867. if(m.IsPcNote())
  868. { // Clear whole cell if clearing PC note
  869. m.Clear();
  870. } else
  871. {
  872. m.note = NOTE_NONE;
  873. if(ITStyle)
  874. m.instr = 0;
  875. }
  876. }
  877. break;
  878. case PatternCursor::instrColumn: // Clear instrument
  879. if(rm.instrument)
  880. {
  881. m.instr = 0;
  882. }
  883. break;
  884. case PatternCursor::volumeColumn: // Clear volume
  885. if(rm.volume)
  886. {
  887. m.volcmd = VOLCMD_NONE;
  888. m.vol = 0;
  889. }
  890. break;
  891. case PatternCursor::effectColumn: // Clear Command
  892. if(rm.command)
  893. {
  894. m.command = CMD_NONE;
  895. if(m.IsPcNote())
  896. {
  897. m.SetValueEffectCol(0);
  898. }
  899. }
  900. break;
  901. case PatternCursor::paramColumn: // Clear Command Param
  902. if(rm.parameter)
  903. {
  904. m.param = 0;
  905. if(m.IsPcNote())
  906. {
  907. m.SetValueEffectCol(0);
  908. if(cell.CompareColumn(m_Selection.GetUpperLeft()) == 0)
  909. {
  910. // If this is the first selected column, update effect column char as well
  911. PatternCursor upper(m_Selection.GetUpperLeft());
  912. upper.Move(0, 0, -1);
  913. m_Selection = PatternRect(upper, m_Selection.GetLowerRight());
  914. }
  915. }
  916. }
  917. break;
  918. }
  919. }
  920. });
  921. // Expand invalidation to the whole column. Needed for:
  922. // - Last column is the effect character (parameter needs to be invalidated, too
  923. // - PC Notes
  924. // - Default volume display is enabled.
  925. PatternCursor endCursor(m_Selection.GetEndRow(), m_Selection.GetEndChannel() + 1);
  926. InvalidateArea(m_Selection.GetUpperLeft(), endCursor);
  927. SetModified();
  928. EndWaitCursor();
  929. SetFocus();
  930. }
  931. void CViewPattern::OnEditCut()
  932. {
  933. OnEditCopy();
  934. OnClearSelection(false);
  935. }
  936. void CViewPattern::OnEditCopy()
  937. {
  938. CModDoc *pModDoc = GetDocument();
  939. if(pModDoc)
  940. {
  941. CopyPattern(m_nPattern, m_Selection);
  942. SetFocus();
  943. }
  944. }
  945. void CViewPattern::StartRecordGroupDragging(const DragItem source)
  946. {
  947. // Drag-select record channels
  948. const auto *modDoc = GetDocument();
  949. if(modDoc == nullptr)
  950. return;
  951. m_initialDragRecordStatus.resize(modDoc->GetNumChannels());
  952. for(CHANNELINDEX chn = 0; chn < modDoc->GetNumChannels(); chn++)
  953. {
  954. m_initialDragRecordStatus[chn] = modDoc->GetChannelRecordGroup(chn);
  955. }
  956. m_Status.reset(psDragging);
  957. m_nDropItem = m_nDragItem = source;
  958. }
  959. void CViewPattern::OnLButtonDown(UINT nFlags, CPoint point)
  960. {
  961. const auto *modDoc = GetDocument();
  962. if(modDoc == nullptr)
  963. return;
  964. const auto &sndFile = modDoc->GetSoundFile();
  965. SetFocus();
  966. m_nDropItem = m_nDragItem = GetDragItem(point, m_rcDragItem);
  967. m_Status.set(psDragging);
  968. m_bInItemRect = true;
  969. m_Status.reset(psShiftDragging);
  970. PatternCursor pointCursor(GetPositionFromPoint(point));
  971. SetCapture();
  972. if(point.x >= m_szHeader.cx && point.y <= m_szHeader.cy - m_szPluginHeader.cy)
  973. {
  974. // Click on channel header
  975. if(nFlags & MK_CONTROL)
  976. TogglePendingMute(pointCursor.GetChannel());
  977. if(nFlags & MK_SHIFT)
  978. {
  979. // Drag-select record channels
  980. StartRecordGroupDragging(m_nDragItem);
  981. }
  982. } else if(point.x >= m_szHeader.cx && point.y > m_szHeader.cy)
  983. {
  984. // Click on pattern data
  985. if(IsLiveRecord() && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOFOLLOWONCLICK))
  986. {
  987. SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 0);
  988. }
  989. if(CMainFrame::GetInputHandler()->SelectionPressed()
  990. && (m_Status[psShiftSelect]
  991. || m_Selection.GetUpperLeft() == m_Selection.GetLowerRight()
  992. || !m_Selection.Contains(pointCursor)))
  993. {
  994. // Shift pressed -> set 2nd selection point
  995. // This behaviour is only used if:
  996. // * Shift-click has previously been used since the shift key has been pressed down (psShiftSelect flag is set),
  997. // * No selection has been made yet, or
  998. // * Shift-clicking outside the current selection.
  999. // This is necessary so that selections can still be moved properly while the shift button is pressed (for copy-move).
  1000. DragToSel(pointCursor, true, true);
  1001. m_Status.set(psShiftSelect);
  1002. } else
  1003. {
  1004. // Set first selection point
  1005. m_StartSel = pointCursor;
  1006. if(m_StartSel.GetChannel() < sndFile.GetNumChannels())
  1007. {
  1008. m_Status.set(psMouseDragSelect);
  1009. if(m_Status[psCtrlDragSelect])
  1010. {
  1011. SetCurSel(m_StartSel);
  1012. }
  1013. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_DRAGNDROPEDIT)
  1014. && ((m_Selection.GetUpperLeft() != m_Selection.GetLowerRight()) || m_Status[psCtrlDragSelect])
  1015. && m_Selection.Contains(m_StartSel))
  1016. {
  1017. m_Status.set(psDragnDropEdit);
  1018. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CENTERROW)
  1019. {
  1020. SetCurSel(m_StartSel);
  1021. } else
  1022. {
  1023. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  1024. SetCursorPosition(m_StartSel);
  1025. }
  1026. }
  1027. }
  1028. } else if(point.x < m_szHeader.cx && point.y > m_szHeader.cy)
  1029. {
  1030. // Mark row number => mark whole row (start)
  1031. InvalidateSelection();
  1032. if(pointCursor.GetRow() < sndFile.Patterns[m_nPattern].GetNumRows())
  1033. {
  1034. m_StartSel.Set(pointCursor.GetRow(), 0);
  1035. SetCurSel(m_StartSel, PatternCursor(pointCursor.GetRow(), sndFile.GetNumChannels() - 1, PatternCursor::lastColumn));
  1036. m_Status.set(psRowSelection);
  1037. }
  1038. }
  1039. if(m_nDragItem.IsValid())
  1040. {
  1041. InvalidateRect(&m_rcDragItem, FALSE);
  1042. UpdateWindow();
  1043. }
  1044. }
  1045. void CViewPattern::OnLButtonDblClk(UINT uFlags, CPoint point)
  1046. {
  1047. PatternCursor cursor = GetPositionFromPoint(point);
  1048. if(cursor == m_Cursor && point.y >= m_szHeader.cy)
  1049. {
  1050. // Double-click pattern cell: Select whole column or show cell properties.
  1051. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_DBLCLICKSELECT))
  1052. {
  1053. OnSelectCurrentChannel();
  1054. m_Status.set(psChannelSelection | psDragging);
  1055. return;
  1056. } else
  1057. {
  1058. if(ShowEditWindow())
  1059. return;
  1060. }
  1061. }
  1062. OnLButtonDown(uFlags, point);
  1063. }
  1064. void CViewPattern::OnLButtonUp(UINT nFlags, CPoint point)
  1065. {
  1066. CModDoc *modDoc = GetDocument();
  1067. if(modDoc == nullptr)
  1068. return;
  1069. const auto dragType = m_nDragItem.Type();
  1070. const bool wasDraggingRecordGroup = IsDraggingRecordGroup();
  1071. const bool itemSelected = m_bInItemRect || (dragType == DragItem::ChannelHeader);
  1072. m_bInItemRect = false;
  1073. ResetRecordGroupDragging();
  1074. ReleaseCapture();
  1075. m_Status.reset(psMouseDragSelect | psRowSelection | psChannelSelection | psDragging);
  1076. // Drag & Drop Editing
  1077. if(m_Status[psDragnDropEdit])
  1078. {
  1079. if(m_Status[psDragnDropping])
  1080. {
  1081. OnDrawDragSel();
  1082. m_Status.reset(psDragnDropping);
  1083. OnDropSelection();
  1084. }
  1085. if(GetPositionFromPoint(point) == m_StartSel)
  1086. {
  1087. SetCursorPosition(m_StartSel);
  1088. }
  1089. SetCursor(CMainFrame::curArrow);
  1090. m_Status.reset(psDragnDropEdit);
  1091. }
  1092. if(dragType != DragItem::ChannelHeader
  1093. && dragType != DragItem::PatternHeader
  1094. && dragType != DragItem::PluginName)
  1095. {
  1096. if((m_nMidRow) && (m_Selection.GetUpperLeft() == m_Selection.GetLowerRight()))
  1097. {
  1098. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  1099. SetCursorPosition(m_Selection.GetUpperLeft());
  1100. //UpdateIndicator();
  1101. }
  1102. }
  1103. if(!itemSelected || !m_nDragItem.IsValid())
  1104. return;
  1105. InvalidateRect(&m_rcDragItem, FALSE);
  1106. const CHANNELINDEX sourceChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
  1107. const CHANNELINDEX targetChn = m_nDropItem.IsValid() ? static_cast<CHANNELINDEX>(m_nDropItem.Value()) : CHANNELINDEX_INVALID;
  1108. switch(m_nDragItem.Type())
  1109. {
  1110. case DragItem::ChannelHeader:
  1111. if(sourceChn == targetChn && targetChn < modDoc->GetNumChannels())
  1112. {
  1113. // Just clicked a channel header...
  1114. if(nFlags & MK_SHIFT)
  1115. {
  1116. // Toggle record state
  1117. modDoc->ToggleChannelRecordGroup(sourceChn, RecordGroup::Group1);
  1118. InvalidateChannelsHeaders(sourceChn);
  1119. } else if(CMainFrame::GetInputHandler()->AltPressed())
  1120. {
  1121. // Solo / Unsolo
  1122. OnSoloChannel(sourceChn);
  1123. } else if(!(nFlags & MK_CONTROL))
  1124. {
  1125. // Mute / Unmute
  1126. OnMuteChannel(sourceChn);
  1127. }
  1128. } else if(!wasDraggingRecordGroup && targetChn < modDoc->GetNumChannels() && m_nDropItem.Type() == DragItem::ChannelHeader)
  1129. {
  1130. // Dragged to other channel header => move or copy channel
  1131. InvalidateRect(&m_rcDropItem, FALSE);
  1132. const bool duplicate = (nFlags & MK_SHIFT) != 0;
  1133. DragChannel(sourceChn, targetChn, 1, duplicate);
  1134. }
  1135. break;
  1136. case DragItem::PatternHeader:
  1137. OnPatternProperties();
  1138. break;
  1139. case DragItem::PluginName:
  1140. if(sourceChn < MAX_BASECHANNELS)
  1141. TogglePluginEditor(sourceChn);
  1142. break;
  1143. }
  1144. m_nDropItem = {};
  1145. }
  1146. void CViewPattern::DragChannel(CHANNELINDEX source, CHANNELINDEX target, CHANNELINDEX numChannels, bool duplicate)
  1147. {
  1148. auto modDoc = GetDocument();
  1149. const CHANNELINDEX newChannels = modDoc->GetNumChannels() + (duplicate ? numChannels : 0);
  1150. std::vector<CHANNELINDEX> channels(newChannels, 0);
  1151. bool modified = duplicate;
  1152. for(CHANNELINDEX chn = 0, fromChn = 0; chn < newChannels; chn++)
  1153. {
  1154. if(chn >= target && chn < target + numChannels)
  1155. {
  1156. channels[chn] = source + chn - target;
  1157. } else
  1158. {
  1159. if(fromChn == source && !duplicate) // Don't want the source channels twice if we're just moving
  1160. {
  1161. fromChn += numChannels;
  1162. }
  1163. channels[chn] = fromChn++;
  1164. }
  1165. if(channels[chn] != chn)
  1166. {
  1167. modified = true;
  1168. }
  1169. }
  1170. if(modified && modDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
  1171. {
  1172. modDoc->UpdateAllViews(this, GeneralHint().Channels().ModType(), this);
  1173. if(duplicate)
  1174. {
  1175. // Number of channels changed: Update channel headers and other information.
  1176. SetCurrentPattern(m_nPattern);
  1177. }
  1178. if(!duplicate)
  1179. {
  1180. const auto oldSel = m_Selection;
  1181. if(auto chn = m_Cursor.GetChannel(); (chn >= source && chn < source + numChannels))
  1182. SetCurrentColumn(target + chn - source, m_Cursor.GetColumnType());
  1183. if(oldSel.GetStartChannel() >= source && oldSel.GetEndChannel() < source + numChannels)
  1184. {
  1185. const auto diff = static_cast<int>(target) - source;
  1186. auto upperLeft = oldSel.GetUpperLeft(), lowerRight = oldSel.GetLowerRight();
  1187. upperLeft.Move(0, diff, 0);
  1188. lowerRight.Move(0, diff, 0);
  1189. SetCurSel(upperLeft, lowerRight);
  1190. }
  1191. }
  1192. InvalidatePattern(true, false);
  1193. SetModified(false);
  1194. }
  1195. }
  1196. void CViewPattern::ShowPatternProperties(PATTERNINDEX pat)
  1197. {
  1198. CModDoc *pModDoc = GetDocument();
  1199. if(pat == PATTERNINDEX_INVALID)
  1200. pat = m_nPattern;
  1201. if(pModDoc && pModDoc->GetSoundFile().Patterns.IsValidPat(pat))
  1202. {
  1203. CPatternPropertiesDlg dlg(*pModDoc, pat, this);
  1204. if(dlg.DoModal() == IDOK)
  1205. {
  1206. UpdateScrollSize();
  1207. InvalidatePattern(true, true);
  1208. SanitizeCursor();
  1209. pModDoc->UpdateAllViews(this, PatternHint(pat).Data(), this);
  1210. }
  1211. }
  1212. }
  1213. void CViewPattern::OnRButtonDown(UINT flags, CPoint pt)
  1214. {
  1215. CModDoc *modDoc = GetDocument();
  1216. HMENU hMenu;
  1217. // Too far left to get a ctx menu:
  1218. if(!modDoc || pt.x < m_szHeader.cx)
  1219. {
  1220. return;
  1221. }
  1222. // Handle drag n drop
  1223. if(m_Status[psDragnDropEdit])
  1224. {
  1225. if(m_Status[psDragnDropping])
  1226. {
  1227. OnDrawDragSel();
  1228. m_Status.reset(psDragnDropping);
  1229. }
  1230. m_Status.reset(psDragnDropEdit | psMouseDragSelect);
  1231. if(m_Status[psDragging])
  1232. {
  1233. m_Status.reset(psDragging);
  1234. m_bInItemRect = false;
  1235. ReleaseCapture();
  1236. }
  1237. SetCursor(CMainFrame::curArrow);
  1238. return;
  1239. }
  1240. if((hMenu = ::CreatePopupMenu()) == NULL)
  1241. {
  1242. return;
  1243. }
  1244. CSoundFile &sndFile = modDoc->GetSoundFile();
  1245. m_MenuCursor = GetPositionFromPoint(pt);
  1246. // Right-click outside single-point selection? Reposition cursor to the new location
  1247. if(!m_Selection.Contains(m_MenuCursor) && m_Selection.GetUpperLeft() == m_Selection.GetLowerRight())
  1248. {
  1249. if(pt.y > m_szHeader.cy)
  1250. {
  1251. //ensure we're not clicking header
  1252. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  1253. SetCursorPosition(m_MenuCursor);
  1254. }
  1255. }
  1256. const CHANNELINDEX nChn = m_MenuCursor.GetChannel();
  1257. const bool inChannelHeader = (pt.y < m_szHeader.cy);
  1258. if((flags & MK_CONTROL) && nChn < sndFile.GetNumChannels() && inChannelHeader)
  1259. {
  1260. // Ctrl+Right-Click: Open quick channel properties.
  1261. ClientToScreen(&pt);
  1262. m_quickChannelProperties.Show(GetDocument(), nChn, pt);
  1263. } else if((flags & MK_SHIFT) && inChannelHeader)
  1264. {
  1265. // Drag-select record channels
  1266. StartRecordGroupDragging(GetDragItem(pt, m_rcDragItem));
  1267. } else if(nChn < sndFile.GetNumChannels() && sndFile.Patterns.IsValidPat(m_nPattern) && !(flags & (MK_CONTROL | MK_SHIFT)))
  1268. {
  1269. CInputHandler *ih = CMainFrame::GetInputHandler();
  1270. //------ Plugin Header Menu --------- :
  1271. if(m_Status[psShowPluginNames] &&
  1272. inChannelHeader && (pt.y > m_szHeader.cy - m_szPluginHeader.cy))
  1273. {
  1274. BuildPluginCtxMenu(hMenu, nChn, sndFile);
  1275. }
  1276. //------ Channel Header Menu ---------- :
  1277. else if(inChannelHeader)
  1278. {
  1279. if(ih->ShiftPressed())
  1280. {
  1281. //Don't bring up menu if shift is pressed, else we won't get button up msg.
  1282. } else
  1283. {
  1284. if(BuildSoloMuteCtxMenu(hMenu, ih, nChn, sndFile))
  1285. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1286. BuildRecordCtxMenu(hMenu, ih, nChn);
  1287. BuildChannelControlCtxMenu(hMenu, ih);
  1288. }
  1289. }
  1290. //------ Standard Menu ---------- :
  1291. else if((pt.x >= m_szHeader.cx) && (pt.y >= m_szHeader.cy))
  1292. {
  1293. // When combining menus, use bitwise ORs to avoid shortcuts
  1294. if(BuildSelectionCtxMenu(hMenu, ih))
  1295. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1296. if(BuildEditCtxMenu(hMenu, ih, modDoc))
  1297. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1298. if(BuildInterpolationCtxMenu(hMenu, ih)
  1299. | BuildTransposeCtxMenu(hMenu, ih))
  1300. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1301. if(BuildVisFXCtxMenu(hMenu, ih)
  1302. | BuildAmplifyCtxMenu(hMenu, ih)
  1303. | BuildSetInstCtxMenu(hMenu, ih))
  1304. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1305. if(BuildPCNoteCtxMenu(hMenu, ih))
  1306. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1307. if(BuildGrowShrinkCtxMenu(hMenu, ih))
  1308. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1309. if(BuildMiscCtxMenu(hMenu, ih))
  1310. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1311. if(BuildRowInsDelCtxMenu(hMenu, ih))
  1312. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  1313. CString s = _T("&Quantize ");
  1314. if(TrackerSettings::Instance().recordQuantizeRows != 0)
  1315. {
  1316. uint32 rows = TrackerSettings::Instance().recordQuantizeRows.Get();
  1317. s += MPT_CFORMAT("(Currently: {} Row{})")(rows, CString(rows == 1 ? _T("") : _T("s")));
  1318. } else
  1319. {
  1320. s += _T("Settings...");
  1321. }
  1322. AppendMenu(hMenu, MF_STRING | (TrackerSettings::Instance().recordQuantizeRows != 0 ? MF_CHECKED : 0), ID_SETQUANTIZE, ih->GetKeyTextFromCommand(kcQuantizeSettings, s));
  1323. }
  1324. ClientToScreen(&pt);
  1325. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
  1326. } else if(nChn >= sndFile.GetNumChannels() && sndFile.GetNumChannels() < sndFile.GetModSpecifications().channelsMax && !(flags & (MK_CONTROL | MK_SHIFT)))
  1327. {
  1328. // Click outside of pattern: Offer easy way to add more channels
  1329. m_MenuCursor.Set(0, sndFile.GetNumChannels() - 1);
  1330. AppendMenu(hMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_AFTER, _T("&Add Channel"));
  1331. ClientToScreen(&pt);
  1332. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
  1333. }
  1334. ::DestroyMenu(hMenu);
  1335. }
  1336. void CViewPattern::OnRButtonUp(UINT nFlags, CPoint point)
  1337. {
  1338. CModDoc *pModDoc = GetDocument();
  1339. if(!pModDoc)
  1340. return;
  1341. ResetRecordGroupDragging();
  1342. const CHANNELINDEX sourceChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
  1343. const CHANNELINDEX targetChn = m_nDropItem.IsValid() ? static_cast<CHANNELINDEX>(m_nDropItem.Value()) : CHANNELINDEX_INVALID;
  1344. switch(m_nDragItem.Type())
  1345. {
  1346. case DragItem::ChannelHeader:
  1347. if(nFlags & MK_SHIFT)
  1348. {
  1349. if(sourceChn < MAX_BASECHANNELS && sourceChn == targetChn)
  1350. {
  1351. pModDoc->ToggleChannelRecordGroup(sourceChn, RecordGroup::Group2);
  1352. InvalidateChannelsHeaders(sourceChn);
  1353. }
  1354. }
  1355. break;
  1356. }
  1357. CModScrollView::OnRButtonUp(nFlags, point);
  1358. }
  1359. BOOL CViewPattern::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
  1360. {
  1361. if(nFlags & MK_CONTROL)
  1362. {
  1363. // Ctrl + mouse wheel: Increment / decrement values
  1364. DataEntry(zDelta > 0, (nFlags & MK_SHIFT) == MK_SHIFT);
  1365. return TRUE;
  1366. }
  1367. if(IsLiveRecord() && !m_Status[psDragActive])
  1368. {
  1369. // During live playback with "follow song" enabled, the mouse wheel can be used to jump forwards and backwards.
  1370. CursorJump(-mpt::signum(zDelta), false);
  1371. return TRUE;
  1372. }
  1373. return CModScrollView::OnMouseWheel(nFlags, zDelta, pt);
  1374. }
  1375. void CViewPattern::OnXButtonUp(UINT nFlags, UINT nButton, CPoint point)
  1376. {
  1377. if(nButton == XBUTTON1)
  1378. OnPrevOrder();
  1379. else if(nButton == XBUTTON2)
  1380. OnNextOrder();
  1381. CModScrollView::OnXButtonUp(nFlags, nButton, point);
  1382. }
  1383. void CViewPattern::OnMouseMove(UINT nFlags, CPoint point)
  1384. {
  1385. CModScrollView::OnMouseMove(nFlags, point);
  1386. const bool isDraggingRecordGroup = IsDraggingRecordGroup();
  1387. if(!m_Status[psDragging] && !isDraggingRecordGroup)
  1388. return;
  1389. // Drag&Drop actions
  1390. if(m_nDragItem.IsValid())
  1391. {
  1392. const CRect oldDropRect = m_rcDropItem;
  1393. const auto oldDropItem = m_nDropItem;
  1394. if(isDraggingRecordGroup)
  1395. {
  1396. // When drag-selecting record channels, ignore y position
  1397. point.y = m_rcDragItem.top;
  1398. }
  1399. m_Status.set(psShiftDragging, (nFlags & MK_SHIFT) != 0);
  1400. m_nDropItem = GetDragItem(point, m_rcDropItem);
  1401. const bool b = (m_nDropItem == m_nDragItem);
  1402. const bool dragChannel = m_nDragItem.Type() == DragItem::ChannelHeader;
  1403. if(b != m_bInItemRect || (m_nDropItem != oldDropItem && dragChannel))
  1404. {
  1405. m_bInItemRect = b;
  1406. InvalidateRect(&m_rcDragItem, FALSE);
  1407. // Drag-select record channels
  1408. if(isDraggingRecordGroup && m_nDropItem.Type() == DragItem::ChannelHeader)
  1409. {
  1410. auto modDoc = GetDocument();
  1411. auto startChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
  1412. auto endChn = static_cast<CHANNELINDEX>(m_nDropItem.Value());
  1413. RecordGroup setRecord = RecordGroup::NoGroup;
  1414. if(m_initialDragRecordStatus[startChn] != RecordGroup::Group1 && (nFlags & MK_LBUTTON))
  1415. setRecord = RecordGroup::Group1;
  1416. else if (m_initialDragRecordStatus[startChn] != RecordGroup::Group2 && (nFlags & MK_RBUTTON))
  1417. setRecord = RecordGroup::Group2;
  1418. if(startChn > endChn)
  1419. std::swap(startChn, endChn);
  1420. CHANNELINDEX numChannels = std::min(modDoc->GetNumChannels(), static_cast<CHANNELINDEX>(m_initialDragRecordStatus.size()));
  1421. for(CHANNELINDEX chn = 0; chn < numChannels; chn++)
  1422. {
  1423. auto oldState = modDoc->GetChannelRecordGroup(chn);
  1424. if(chn >= startChn && chn <= endChn)
  1425. GetDocument()->SetChannelRecordGroup(chn, setRecord);
  1426. else
  1427. GetDocument()->SetChannelRecordGroup(chn, m_initialDragRecordStatus[chn]);
  1428. if(oldState != modDoc->GetChannelRecordGroup(chn))
  1429. InvalidateChannelsHeaders(chn);
  1430. }
  1431. } else
  1432. {
  1433. // Dragging around channel headers? Update move indicator...
  1434. if(m_nDropItem.Type() == DragItem::ChannelHeader)
  1435. InvalidateRect(&m_rcDropItem, FALSE);
  1436. if(oldDropItem.Type() == DragItem::ChannelHeader)
  1437. InvalidateRect(&oldDropRect, FALSE);
  1438. }
  1439. UpdateWindow();
  1440. }
  1441. }
  1442. if(m_Status[psChannelSelection])
  1443. {
  1444. // Double-clicked a pattern cell to select whole channel.
  1445. // Continue dragging to select more channels.
  1446. const CSoundFile *pSndFile = GetSoundFile();
  1447. if(pSndFile->Patterns.IsValidPat(m_nPattern))
  1448. {
  1449. const ROWINDEX lastRow = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
  1450. CHANNELINDEX startChannel = m_Cursor.GetChannel();
  1451. CHANNELINDEX endChannel = GetPositionFromPoint(point).GetChannel();
  1452. m_StartSel = PatternCursor(0, startChannel, (startChannel <= endChannel ? PatternCursor::firstColumn : PatternCursor::lastColumn));
  1453. PatternCursor endSel = PatternCursor(lastRow, endChannel, (startChannel <= endChannel ? PatternCursor::lastColumn : PatternCursor::firstColumn));
  1454. DragToSel(endSel, true, false, false);
  1455. }
  1456. } else if(m_Status[psRowSelection] && point.y > m_szHeader.cy)
  1457. {
  1458. // Mark row number => mark whole row (continue)
  1459. InvalidateSelection();
  1460. PatternCursor cursor(GetPositionFromPoint(point));
  1461. cursor.SetColumn(GetDocument()->GetNumChannels() - 1, PatternCursor::lastColumn);
  1462. DragToSel(cursor, false, true, false);
  1463. } else if(m_Status[psMouseDragSelect])
  1464. {
  1465. PatternCursor cursor(GetPositionFromPoint(point));
  1466. const CSoundFile *pSndFile = GetSoundFile();
  1467. if(pSndFile != nullptr && m_nPattern < pSndFile->Patterns.Size())
  1468. {
  1469. ROWINDEX row = cursor.GetRow();
  1470. LimitMax(row, pSndFile->Patterns[m_nPattern].GetNumRows() - 1);
  1471. cursor.SetRow(row);
  1472. }
  1473. // Drag & Drop editing
  1474. if(m_Status[psDragnDropEdit])
  1475. {
  1476. const bool moved = m_DragPos.GetChannel() != cursor.GetChannel() || m_DragPos.GetRow() != cursor.GetRow();
  1477. if(!m_Status[psDragnDropping])
  1478. {
  1479. SetCursor(CMainFrame::curDragging);
  1480. }
  1481. if(!m_Status[psDragnDropping] || moved)
  1482. {
  1483. if(m_Status[psDragnDropping])
  1484. OnDrawDragSel();
  1485. m_Status.reset(psDragnDropping);
  1486. DragToSel(cursor, true, true, true);
  1487. m_DragPos = cursor;
  1488. m_Status.set(psDragnDropping);
  1489. OnDrawDragSel();
  1490. }
  1491. } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CENTERROW)
  1492. {
  1493. // Default: selection
  1494. DragToSel(cursor, true, true);
  1495. } else
  1496. {
  1497. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  1498. SetCursorPosition(cursor);
  1499. }
  1500. }
  1501. }
  1502. void CViewPattern::OnEditSelectAll()
  1503. {
  1504. const CSoundFile *pSndFile = GetSoundFile();
  1505. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
  1506. {
  1507. SetCurSel(PatternCursor(0), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn));
  1508. }
  1509. }
  1510. void CViewPattern::OnEditSelectChannel()
  1511. {
  1512. const CSoundFile *pSndFile = GetSoundFile();
  1513. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
  1514. {
  1515. SetCurSel(PatternCursor(0, m_MenuCursor.GetChannel()), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, m_MenuCursor.GetChannel(), PatternCursor::lastColumn));
  1516. }
  1517. }
  1518. void CViewPattern::OnSelectCurrentChannel()
  1519. {
  1520. const CSoundFile *pSndFile = GetSoundFile();
  1521. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
  1522. {
  1523. PatternCursor beginSel(0, GetCurrentChannel());
  1524. PatternCursor endSel(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, GetCurrentChannel(), PatternCursor::lastColumn);
  1525. // If column is already selected, select the current pattern
  1526. if((beginSel == m_Selection.GetUpperLeft()) && (endSel == m_Selection.GetLowerRight()))
  1527. {
  1528. beginSel.Set(0, 0);
  1529. endSel.Set(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn);
  1530. }
  1531. SetCurSel(beginSel, endSel);
  1532. }
  1533. }
  1534. void CViewPattern::OnSelectCurrentColumn()
  1535. {
  1536. const CSoundFile *pSndFile = GetSoundFile();
  1537. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
  1538. {
  1539. SetCurSel(PatternCursor(0, m_Cursor), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, m_Cursor));
  1540. }
  1541. }
  1542. void CViewPattern::OnChannelReset()
  1543. {
  1544. ResetChannel(m_MenuCursor.GetChannel());
  1545. }
  1546. // Reset all channel variables
  1547. void CViewPattern::ResetChannel(CHANNELINDEX chn)
  1548. {
  1549. CModDoc *pModDoc = GetDocument();
  1550. if(pModDoc == nullptr)
  1551. return;
  1552. CSoundFile &sndFile = pModDoc->GetSoundFile();
  1553. CriticalSection cs;
  1554. if(!pModDoc->IsChannelMuted(chn))
  1555. {
  1556. // Cut playing notes
  1557. sndFile.ChnSettings[chn].dwFlags.set(CHN_MUTE);
  1558. pModDoc->UpdateChannelMuteStatus(chn);
  1559. sndFile.ChnSettings[chn].dwFlags.reset(CHN_MUTE);
  1560. }
  1561. sndFile.m_PlayState.Chn[chn].Reset(ModChannel::resetTotal, sndFile, chn, CSoundFile::GetChannelMuteFlag());
  1562. }
  1563. void CViewPattern::OnMuteFromClick()
  1564. {
  1565. OnMuteChannel(m_MenuCursor.GetChannel());
  1566. }
  1567. void CViewPattern::OnMuteChannel(CHANNELINDEX chn)
  1568. {
  1569. CModDoc *pModDoc = GetDocument();
  1570. if(pModDoc)
  1571. {
  1572. pModDoc->SoloChannel(chn, false);
  1573. pModDoc->MuteChannel(chn, !pModDoc->IsChannelMuted(chn));
  1574. //If we just unmuted a channel, make sure none are still considered "solo".
  1575. if(!pModDoc->IsChannelMuted(chn))
  1576. {
  1577. for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++)
  1578. {
  1579. pModDoc->SoloChannel(i, false);
  1580. }
  1581. }
  1582. InvalidateChannelsHeaders();
  1583. pModDoc->UpdateAllViews(this, GeneralHint(chn).Channels());
  1584. }
  1585. }
  1586. void CViewPattern::OnSoloFromClick()
  1587. {
  1588. OnSoloChannel(m_MenuCursor.GetChannel());
  1589. }
  1590. // When trying to solo a channel that is already the only unmuted channel,
  1591. // this will result in unmuting all channels, in order to satisfy user habits.
  1592. // In all other cases, soloing a channel unsoloes all and mutes all except this channel
  1593. void CViewPattern::OnSoloChannel(CHANNELINDEX chn)
  1594. {
  1595. CModDoc *pModDoc = GetDocument();
  1596. if(pModDoc == nullptr)
  1597. return;
  1598. if(chn >= pModDoc->GetNumChannels())
  1599. {
  1600. return;
  1601. }
  1602. if(pModDoc->IsChannelSolo(chn))
  1603. {
  1604. bool nChnIsOnlyUnMutedChan = true;
  1605. for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++) //check status of all other chans
  1606. {
  1607. if(i != chn && !pModDoc->IsChannelMuted(i))
  1608. {
  1609. nChnIsOnlyUnMutedChan = false; //found a channel that isn't muted!
  1610. break;
  1611. }
  1612. }
  1613. if(nChnIsOnlyUnMutedChan) // this is the only playable channel and it is already soloed -> Unmute all
  1614. {
  1615. OnUnmuteAll();
  1616. return;
  1617. }
  1618. }
  1619. for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++)
  1620. {
  1621. pModDoc->MuteChannel(i, !(i == chn)); //mute all chans except nChn, unmute nChn
  1622. pModDoc->SoloChannel(i, (i == chn)); //unsolo all chans except nChn, solo nChn
  1623. }
  1624. InvalidateChannelsHeaders();
  1625. pModDoc->UpdateAllViews(this, GeneralHint(chn).Channels());
  1626. }
  1627. void CViewPattern::OnRecordSelect()
  1628. {
  1629. CModDoc *pModDoc = GetDocument();
  1630. if(pModDoc)
  1631. {
  1632. CHANNELINDEX chn = m_MenuCursor.GetChannel();
  1633. if(chn < pModDoc->GetNumChannels())
  1634. {
  1635. pModDoc->ToggleChannelRecordGroup(chn, RecordGroup::Group1);
  1636. InvalidateChannelsHeaders(chn);
  1637. }
  1638. }
  1639. }
  1640. void CViewPattern::OnSplitRecordSelect()
  1641. {
  1642. CModDoc *pModDoc = GetDocument();
  1643. if(pModDoc)
  1644. {
  1645. CHANNELINDEX chn = m_MenuCursor.GetChannel();
  1646. if(chn < pModDoc->GetNumChannels())
  1647. {
  1648. pModDoc->ToggleChannelRecordGroup(chn, RecordGroup::Group2);
  1649. InvalidateChannelsHeaders(chn);
  1650. }
  1651. }
  1652. }
  1653. void CViewPattern::OnUnmuteAll()
  1654. {
  1655. CModDoc *pModDoc = GetDocument();
  1656. if(pModDoc)
  1657. {
  1658. const CHANNELINDEX numChannels = pModDoc->GetNumChannels();
  1659. for(CHANNELINDEX chn = 0; chn < numChannels; chn++)
  1660. {
  1661. pModDoc->MuteChannel(chn, false);
  1662. pModDoc->SoloChannel(chn, false);
  1663. }
  1664. InvalidateChannelsHeaders();
  1665. }
  1666. }
  1667. bool CViewPattern::InsertOrDeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit, bool deleteRows)
  1668. {
  1669. CModDoc &modDoc = *GetDocument();
  1670. CSoundFile &sndFile = *GetSoundFile();
  1671. if(!sndFile.Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled_bmsg())
  1672. return false;
  1673. LimitMax(lastChn, CHANNELINDEX(sndFile.GetNumChannels() - 1));
  1674. if(firstChn > lastChn)
  1675. return false;
  1676. const auto selection = (firstChn != lastChn || m_Selection.GetNumRows() > 1) ? PatternRect{{m_Selection.GetStartRow(), firstChn, PatternCursor::firstColumn}, {m_Selection.GetEndRow(), lastChn, PatternCursor::lastColumn}} : m_Selection;
  1677. const ROWINDEX numRows = selection.GetNumRows();
  1678. const char *undoDescription = "";
  1679. if(deleteRows)
  1680. undoDescription = numRows != 1 ? "Delete Rows" : "Delete Row";
  1681. else
  1682. undoDescription = numRows != 1 ? "Insert Rows" : "Insert Row";
  1683. const ROWINDEX startRow = selection.GetStartRow();
  1684. const CHANNELINDEX numChannels = lastChn - firstChn + 1;
  1685. std::vector<PATTERNINDEX> patterns;
  1686. if(globalEdit)
  1687. {
  1688. auto &order = Order();
  1689. const auto start = order.begin() + GetCurrentOrder();
  1690. const auto end = std::find(start, order.end(), order.GetInvalidPatIndex());
  1691. // As this is a global operation, ensure that all modified patterns are unique
  1692. bool orderListChanged = false;
  1693. const ORDERINDEX ordEnd = GetCurrentOrder() + static_cast<ORDERINDEX>(std::distance(start, end));
  1694. for(ORDERINDEX ord = GetCurrentOrder(); ord < ordEnd; ord++)
  1695. {
  1696. const auto pat = order[ord];
  1697. if(pat != order.EnsureUnique(ord))
  1698. orderListChanged = true;
  1699. }
  1700. if(orderListChanged)
  1701. modDoc.UpdateAllViews(this, SequenceHint().Data(), nullptr);
  1702. patterns.assign(start, end);
  1703. } else
  1704. {
  1705. patterns = {m_nPattern};
  1706. }
  1707. // Backup source data and create undo points
  1708. std::vector<ModCommand> patternData;
  1709. if(!deleteRows)
  1710. patternData.insert(patternData.begin(), numRows * numChannels, ModCommand{});
  1711. bool first = true;
  1712. for(auto pat : patterns)
  1713. {
  1714. if(!sndFile.Patterns.IsValidPat(pat))
  1715. continue;
  1716. const auto &pattern = sndFile.Patterns[pat];
  1717. const ROWINDEX firstRow = first ? startRow : 0;
  1718. for(ROWINDEX row = firstRow; row < pattern.GetNumRows(); row++)
  1719. {
  1720. const auto *m = pattern.GetpModCommand(row, firstChn);
  1721. patternData.insert(patternData.end(), m, m + numChannels);
  1722. }
  1723. modDoc.GetPatternUndo().PrepareUndo(pat, firstChn, firstRow, numChannels, pattern.GetNumRows(), undoDescription, !first);
  1724. first = false;
  1725. }
  1726. if(deleteRows)
  1727. patternData.insert(patternData.end(), numRows * numChannels, ModCommand{});
  1728. // Now do the actual shifting
  1729. auto src = patternData.cbegin();
  1730. if(deleteRows)
  1731. src += numRows * numChannels;
  1732. PATTERNINDEX firstNewPattern = m_nPattern;
  1733. first = true;
  1734. for(auto pat : patterns)
  1735. {
  1736. if(!sndFile.Patterns.IsValidPat(pat))
  1737. continue;
  1738. auto &pattern = sndFile.Patterns[pat];
  1739. for(ROWINDEX row = first ? startRow : 0; row < pattern.GetNumRows(); row++, src += numChannels)
  1740. {
  1741. ModCommand *dest = pattern.GetpModCommand(row, firstChn);
  1742. std::copy(src, src + numChannels, dest);
  1743. }
  1744. if(first)
  1745. firstNewPattern = pat;
  1746. first = false;
  1747. modDoc.UpdateAllViews(this, PatternHint(pat).Data(), this);
  1748. }
  1749. SetModified();
  1750. SetCurrentPattern(firstNewPattern);
  1751. InvalidatePattern();
  1752. SetCursorPosition(selection.GetUpperLeft());
  1753. SetCurSel(selection);
  1754. return true;
  1755. }
  1756. void CViewPattern::DeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit)
  1757. {
  1758. InsertOrDeleteRows(firstChn, lastChn, globalEdit, true);
  1759. }
  1760. void CViewPattern::OnDeleteRow()
  1761. {
  1762. DeleteRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel());
  1763. }
  1764. void CViewPattern::OnDeleteWholeRow()
  1765. {
  1766. DeleteRows(0, GetSoundFile()->GetNumChannels() - 1);
  1767. }
  1768. void CViewPattern::OnDeleteRowGlobal()
  1769. {
  1770. DeleteRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel(), true);
  1771. }
  1772. void CViewPattern::OnDeleteWholeRowGlobal()
  1773. {
  1774. DeleteRows(0, GetSoundFile()->GetNumChannels() - 1, true);
  1775. }
  1776. void CViewPattern::InsertRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit)
  1777. {
  1778. InsertOrDeleteRows(firstChn, lastChn, globalEdit, false);
  1779. }
  1780. void CViewPattern::OnInsertRow()
  1781. {
  1782. InsertRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel());
  1783. }
  1784. void CViewPattern::OnInsertWholeRow()
  1785. {
  1786. InsertRows(0, GetSoundFile()->GetNumChannels() - 1);
  1787. }
  1788. void CViewPattern::OnInsertRowGlobal()
  1789. {
  1790. InsertRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel(), true);
  1791. }
  1792. void CViewPattern::OnInsertWholeRowGlobal()
  1793. {
  1794. InsertRows(0, GetSoundFile()->GetNumChannels() - 1, true);
  1795. }
  1796. void CViewPattern::OnSplitPattern()
  1797. {
  1798. COrderList &orderList = static_cast<CCtrlPatterns *>(GetControlDlg())->GetOrderList();
  1799. CSoundFile &sndFile = *GetSoundFile();
  1800. const auto &specs = sndFile.GetModSpecifications();
  1801. const PATTERNINDEX sourcePat = m_nPattern;
  1802. const ROWINDEX splitRow = m_MenuCursor.GetRow();
  1803. if(splitRow < 1 || !sndFile.Patterns.IsValidPat(sourcePat) || !sndFile.Patterns[sourcePat].IsValidRow(splitRow))
  1804. {
  1805. MessageBeep(MB_ICONWARNING);
  1806. return;
  1807. }
  1808. // Create a new pattern (ignore if it's too big for this format - if it is, then the source pattern already was too big, too)
  1809. CriticalSection cs;
  1810. const ROWINDEX numSplitRows = sndFile.Patterns[sourcePat].GetNumRows() - splitRow;
  1811. const PATTERNINDEX newPat = sndFile.Patterns.InsertAny(std::max(specs.patternRowsMin, numSplitRows), false);
  1812. if(newPat == PATTERNINDEX_INVALID)
  1813. {
  1814. cs.Leave();
  1815. Reporting::Error(MPT_AFORMAT("Pattern limit of the {} format ({} patterns) has been reached.")(mpt::ToUpperCaseAscii(specs.fileExtension), specs.patternsMax), "Split Pattern");
  1816. return;
  1817. }
  1818. auto &sourcePattern = sndFile.Patterns[sourcePat];
  1819. auto &newPattern = sndFile.Patterns[newPat];
  1820. auto &undo = GetDocument()->GetPatternUndo();
  1821. undo.PrepareUndo(sourcePat, 0, splitRow, sourcePattern.GetNumChannels(), numSplitRows, "Split Pattern");
  1822. undo.PrepareUndo(newPat, 0, 0, newPattern.GetNumChannels(), newPattern.GetNumRows(), "Split Pattern", true);
  1823. auto copyStart = sourcePattern.begin() + sourcePattern.GetNumChannels() * splitRow;
  1824. std::copy(copyStart, sourcePattern.end(), newPattern.begin());
  1825. // Reduce the row number or insert pattern breaks, if the patterns are too small for the format
  1826. sourcePattern.Resize(std::max(specs.patternRowsMin, splitRow));
  1827. if(splitRow != sourcePattern.GetNumRows())
  1828. {
  1829. std::fill(copyStart, sourcePattern.end(), ModCommand::Empty());
  1830. sourcePattern.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(splitRow - 1).RetryNextRow());
  1831. }
  1832. if(numSplitRows != newPattern.GetNumRows())
  1833. {
  1834. newPattern.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(numSplitRows - 1).RetryNextRow());
  1835. }
  1836. // Update every occurrence of the split pattern in all order lists
  1837. auto editOrd = GetCurrentOrder();
  1838. for(SEQUENCEINDEX seq = 0; seq < sndFile.Order.GetNumSequences(); seq++)
  1839. {
  1840. const bool isCurrentSeq = (seq == sndFile.Order.GetCurrentSequenceIndex());
  1841. bool editedSeq = false;
  1842. auto &order = sndFile.Order(seq);
  1843. for(ORDERINDEX i = 0; i < order.GetLength(); i++)
  1844. {
  1845. if(order[i] == sourcePat)
  1846. {
  1847. if(!order.insert(i + 1, 1, newPat))
  1848. continue;
  1849. editedSeq = true;
  1850. if(isCurrentSeq)
  1851. orderList.InsertUpdatePlaystate(i, i + 1);
  1852. i++;
  1853. // Slide the current selection accordingly so it doesn't end up in the wrong id
  1854. if(i < editOrd && isCurrentSeq)
  1855. editOrd++;
  1856. }
  1857. }
  1858. if(editedSeq)
  1859. GetDocument()->UpdateAllViews(nullptr, SequenceHint(seq).Data(), this);
  1860. }
  1861. orderList.SetSelection(editOrd + 1);
  1862. SetCurrentRow(0);
  1863. SetModified(true);
  1864. GetDocument()->UpdateAllViews(nullptr, PatternHint(newPat).Names().Data(), this);
  1865. }
  1866. void CViewPattern::OnEditGoto()
  1867. {
  1868. CModDoc *pModDoc = GetDocument();
  1869. if(!pModDoc)
  1870. return;
  1871. ORDERINDEX curOrder = GetCurrentOrder();
  1872. CHANNELINDEX curChannel = GetCurrentChannel() + 1;
  1873. CPatternGotoDialog dlg(this, GetCurrentRow(), curChannel, m_nPattern, curOrder, pModDoc->GetSoundFile());
  1874. if(dlg.DoModal() == IDOK)
  1875. {
  1876. if(dlg.m_nPattern != m_nPattern)
  1877. SetCurrentPattern(dlg.m_nPattern);
  1878. if(dlg.m_nOrder != curOrder)
  1879. SetCurrentOrder(dlg.m_nOrder);
  1880. if(dlg.m_nChannel != curChannel)
  1881. SetCurrentColumn(dlg.m_nChannel - 1);
  1882. if(dlg.m_nRow != GetCurrentRow())
  1883. SetCurrentRow(dlg.m_nRow);
  1884. CriticalSection cs;
  1885. pModDoc->SetElapsedTime(dlg.m_nOrder, dlg.m_nRow, false);
  1886. }
  1887. return;
  1888. }
  1889. void CViewPattern::OnPatternStep()
  1890. {
  1891. PatternStep();
  1892. }
  1893. void CViewPattern::PatternStep(ROWINDEX row)
  1894. {
  1895. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  1896. CModDoc *pModDoc = GetDocument();
  1897. if(pMainFrm != nullptr && pModDoc != nullptr)
  1898. {
  1899. CSoundFile &sndFile = pModDoc->GetSoundFile();
  1900. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  1901. return;
  1902. CriticalSection cs;
  1903. // In case we were previously in smooth scrolling mode during live playback, the pattern might be misaligned.
  1904. if(GetSmoothScrollOffset() != 0)
  1905. InvalidatePattern(true, true);
  1906. // Cut instruments/samples in virtual channels
  1907. for(CHANNELINDEX i = sndFile.GetNumChannels(); i < MAX_CHANNELS; i++)
  1908. {
  1909. sndFile.m_PlayState.Chn[i].dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
  1910. }
  1911. sndFile.LoopPattern(m_nPattern);
  1912. sndFile.m_PlayState.m_nNextRow = row == ROWINDEX_INVALID ? GetCurrentRow() : row;
  1913. sndFile.m_SongFlags.reset(SONG_PAUSED);
  1914. sndFile.m_SongFlags.set(SONG_STEP);
  1915. SetPlayCursor(m_nPattern, sndFile.m_PlayState.m_nNextRow, 0);
  1916. cs.Leave();
  1917. if(pMainFrm->GetModPlaying() != pModDoc)
  1918. {
  1919. pModDoc->SetFollowWnd(m_hWnd);
  1920. pMainFrm->PlayMod(pModDoc);
  1921. }
  1922. pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
  1923. if(row == ROWINDEX_INVALID)
  1924. {
  1925. SetCurrentRow(GetCurrentRow() + 1,
  1926. (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) || // Wrap around to next pattern if continous scroll is enabled...
  1927. (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)); // ...or otherwise if cursor wrap is enabled.
  1928. }
  1929. SetFocus();
  1930. }
  1931. }
  1932. // Copy cursor to internal clipboard
  1933. void CViewPattern::OnCursorCopy()
  1934. {
  1935. const CSoundFile *pSndFile = GetSoundFile();
  1936. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  1937. {
  1938. return;
  1939. }
  1940. const ModCommand &m = GetCursorCommand();
  1941. switch(m_Cursor.GetColumnType())
  1942. {
  1943. case PatternCursor::noteColumn:
  1944. case PatternCursor::instrColumn:
  1945. m_cmdOld.note = m.note;
  1946. m_cmdOld.instr = m.instr;
  1947. SendCtrlMessage(CTRLMSG_SETCURRENTINSTRUMENT, m_cmdOld.instr);
  1948. break;
  1949. case PatternCursor::volumeColumn:
  1950. m_cmdOld.volcmd = m.volcmd;
  1951. m_cmdOld.vol = m.vol;
  1952. break;
  1953. case PatternCursor::effectColumn:
  1954. case PatternCursor::paramColumn:
  1955. m_cmdOld.command = m.command;
  1956. m_cmdOld.param = m.param;
  1957. break;
  1958. }
  1959. }
  1960. // Paste cursor from internal clipboard
  1961. void CViewPattern::OnCursorPaste()
  1962. {
  1963. if(!IsEditingEnabled_bmsg())
  1964. {
  1965. return;
  1966. }
  1967. PrepareUndo(m_Cursor, m_Cursor, "Cursor Paste");
  1968. PatternCursor::Columns column = m_Cursor.GetColumnType();
  1969. ModCommand &m = GetCursorCommand();
  1970. switch(column)
  1971. {
  1972. case PatternCursor::noteColumn:
  1973. m.note = m_cmdOld.note;
  1974. [[fallthrough]];
  1975. case PatternCursor::instrColumn:
  1976. m.instr = m_cmdOld.instr;
  1977. break;
  1978. case PatternCursor::volumeColumn:
  1979. m.vol = m_cmdOld.vol;
  1980. m.volcmd = m_cmdOld.volcmd;
  1981. break;
  1982. case PatternCursor::effectColumn:
  1983. case PatternCursor::paramColumn:
  1984. m.command = m_cmdOld.command;
  1985. m.param = m_cmdOld.param;
  1986. break;
  1987. }
  1988. SetModified(false);
  1989. // Preview Row
  1990. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !IsLiveRecord())
  1991. {
  1992. PatternStep(GetCurrentRow());
  1993. }
  1994. if(GetSoundFile()->IsPaused() || !m_Status[psFollowSong] || (CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->GetFollowSong(GetDocument()) != m_hWnd))
  1995. {
  1996. InvalidateCell(m_Cursor);
  1997. SetCurrentRow(GetCurrentRow() + m_nSpacing);
  1998. SetSelToCursor();
  1999. }
  2000. }
  2001. void CViewPattern::OnVisualizeEffect()
  2002. {
  2003. CModDoc *pModDoc = GetDocument();
  2004. if(pModDoc != nullptr && pModDoc->GetSoundFile().Patterns.IsValidPat(m_nPattern))
  2005. {
  2006. const ROWINDEX row0 = m_Selection.GetStartRow(), row1 = m_Selection.GetEndRow();
  2007. const CHANNELINDEX nchn = m_Selection.GetStartChannel();
  2008. if(m_pEffectVis)
  2009. {
  2010. // Window already there, update data
  2011. m_pEffectVis->UpdateSelection(row0, row1, nchn, m_nPattern);
  2012. } else
  2013. {
  2014. // Open window & send data
  2015. CriticalSection cs;
  2016. try
  2017. {
  2018. m_pEffectVis = std::make_unique<CEffectVis>(this, row0, row1, nchn, *pModDoc, m_nPattern);
  2019. m_pEffectVis->OpenEditor(CMainFrame::GetMainFrame());
  2020. // HACK: to get status window set up; must create clear destinction between
  2021. // construction, 1st draw code and all draw code.
  2022. m_pEffectVis->OnSize(0, 0, 0);
  2023. } catch(mpt::out_of_memory e)
  2024. {
  2025. mpt::delete_out_of_memory(e);
  2026. }
  2027. }
  2028. }
  2029. }
  2030. // Helper function for sweeping the pattern up and down to find suitable start and end points for interpolation.
  2031. // startCond must return true for the start row, endCond must return true for the end row.
  2032. PatternRect CViewPattern::SweepPattern(bool(*startCond)(const ModCommand &), bool(*endCond)(const ModCommand &, const ModCommand &)) const
  2033. {
  2034. const auto &pattern = GetSoundFile()->Patterns[m_nPattern];
  2035. const ROWINDEX numRows = pattern.GetNumRows();
  2036. const ROWINDEX cursorRow = m_Selection.GetStartRow();
  2037. if(cursorRow >= numRows)
  2038. return {};
  2039. const ModCommand *start = pattern.GetpModCommand(cursorRow, m_Selection.GetStartChannel()), *end = start;
  2040. // Sweep up
  2041. ROWINDEX startRow = ROWINDEX_INVALID;
  2042. for(ROWINDEX row = 0; row <= cursorRow; row++, start -= pattern.GetNumChannels())
  2043. {
  2044. if(startCond(*start))
  2045. {
  2046. startRow = cursorRow - row;
  2047. break;
  2048. }
  2049. }
  2050. if(startRow == ROWINDEX_INVALID)
  2051. return {};
  2052. // Sweep down
  2053. ROWINDEX endRow = ROWINDEX_INVALID;
  2054. for(ROWINDEX row = cursorRow; row < numRows; row++, end += pattern.GetNumChannels())
  2055. {
  2056. if(endCond(*start, *end))
  2057. {
  2058. endRow = row;
  2059. break;
  2060. }
  2061. }
  2062. if(endRow == ROWINDEX_INVALID)
  2063. return {};
  2064. return {PatternCursor(startRow, m_Selection.GetUpperLeft()), PatternCursor(endRow, m_Selection.GetUpperLeft())};
  2065. }
  2066. void CViewPattern::Interpolate(PatternCursor::Columns type)
  2067. {
  2068. CSoundFile *sndFile = GetSoundFile();
  2069. if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled())
  2070. return;
  2071. bool changed = false;
  2072. std::vector<CHANNELINDEX> validChans;
  2073. if(type == PatternCursor::effectColumn || type == PatternCursor::paramColumn)
  2074. {
  2075. std::vector<CHANNELINDEX> effectChans;
  2076. std::vector<CHANNELINDEX> paramChans;
  2077. ListChansWhereColSelected(PatternCursor::effectColumn, effectChans);
  2078. ListChansWhereColSelected(PatternCursor::paramColumn, paramChans);
  2079. validChans.resize(effectChans.size() + paramChans.size());
  2080. validChans.resize(std::set_union(effectChans.begin(), effectChans.end(), paramChans.begin(), paramChans.end(), validChans.begin()) - validChans.begin());
  2081. } else
  2082. {
  2083. ListChansWhereColSelected(type, validChans);
  2084. }
  2085. if(m_Selection.GetUpperLeft() == m_Selection.GetLowerRight() && !validChans.empty())
  2086. {
  2087. // No selection has been made: Interpolate between closest non-zero values in this column.
  2088. PatternRect sweepSelection;
  2089. switch(type)
  2090. {
  2091. case PatternCursor::noteColumn:
  2092. // Allow note-to-note interpolation only.
  2093. sweepSelection = SweepPattern(
  2094. [](const ModCommand &start) { return start.note != NOTE_NONE; },
  2095. [](const ModCommand &start, const ModCommand &end) { return start.IsNote() && end.IsNote(); });
  2096. break;
  2097. case PatternCursor::instrColumn:
  2098. // Allow interpolation between same instrument, as long as it's not a PC note.
  2099. sweepSelection = SweepPattern(
  2100. [](const ModCommand &start) { return start.instr != 0 && !start.IsPcNote(); },
  2101. [](const ModCommand &start, const ModCommand &end) { return end.instr == start.instr; });
  2102. break;
  2103. case PatternCursor::volumeColumn:
  2104. // Allow interpolation between same volume effect, as long as it's not a PC note.
  2105. sweepSelection = SweepPattern(
  2106. [](const ModCommand &start) { return start.volcmd != VOLCMD_NONE && !start.IsPcNote(); },
  2107. [](const ModCommand &start, const ModCommand &end) { return end.volcmd == start.volcmd && !end.IsPcNote(); });
  2108. break;
  2109. case PatternCursor::effectColumn:
  2110. case PatternCursor::paramColumn:
  2111. // Allow interpolation between same effect, or anything if it's a PC note.
  2112. sweepSelection = SweepPattern(
  2113. [](const ModCommand &start) { return start.command != CMD_NONE || start.IsPcNote(); },
  2114. [](const ModCommand &start, const ModCommand &end) { return (end.command == start.command || start.IsPcNote()) && (!start.IsPcNote() || end.IsPcNote()); });
  2115. break;
  2116. }
  2117. if(sweepSelection.GetNumRows() > 1)
  2118. {
  2119. // Found usable end and start commands: Extend selection.
  2120. SetCurSel(sweepSelection);
  2121. }
  2122. }
  2123. const ROWINDEX row0 = m_Selection.GetStartRow(), row1 = m_Selection.GetEndRow();
  2124. //for all channels where type is selected
  2125. for(auto nchn : validChans)
  2126. {
  2127. if(!IsInterpolationPossible(row0, row1, nchn, type))
  2128. continue; //skip chans where interpolation isn't possible
  2129. if(!changed) //ensure we save undo buffer only before any channels are interpolated
  2130. {
  2131. const char *description = "";
  2132. switch(type)
  2133. {
  2134. case PatternCursor::noteColumn:
  2135. description = "Interpolate Note Column";
  2136. break;
  2137. case PatternCursor::instrColumn:
  2138. description = "Interpolate Instrument Column";
  2139. break;
  2140. case PatternCursor::volumeColumn:
  2141. description = "Interpolate Volume Column";
  2142. break;
  2143. case PatternCursor::effectColumn:
  2144. case PatternCursor::paramColumn:
  2145. description = "Interpolate Effect Column";
  2146. break;
  2147. }
  2148. PrepareUndo(m_Selection, description);
  2149. }
  2150. bool doPCinterpolation = false;
  2151. int vsrc, vdest, vcmd = 0, verr = 0, distance = row1 - row0;
  2152. const ModCommand srcCmd = *sndFile->Patterns[m_nPattern].GetpModCommand(row0, nchn);
  2153. const ModCommand destCmd = *sndFile->Patterns[m_nPattern].GetpModCommand(row1, nchn);
  2154. ModCommand::NOTE PCnote = NOTE_NONE;
  2155. uint16 PCinst = 0, PCparam = 0;
  2156. switch(type)
  2157. {
  2158. case PatternCursor::noteColumn:
  2159. vsrc = srcCmd.note;
  2160. vdest = destCmd.note;
  2161. vcmd = srcCmd.instr;
  2162. verr = (distance * (NOTE_MAX - 1)) / NOTE_MAX;
  2163. if(srcCmd.note == NOTE_NONE)
  2164. {
  2165. vsrc = vdest;
  2166. vcmd = destCmd.note;
  2167. } else if(destCmd.note == NOTE_NONE)
  2168. {
  2169. vdest = vsrc;
  2170. }
  2171. break;
  2172. case PatternCursor::instrColumn:
  2173. vsrc = srcCmd.instr;
  2174. vdest = destCmd.instr;
  2175. verr = (distance * 63) / 128;
  2176. if(srcCmd.instr == 0)
  2177. {
  2178. vsrc = vdest;
  2179. vcmd = destCmd.instr;
  2180. } else if(destCmd.instr == 0)
  2181. {
  2182. vdest = vsrc;
  2183. }
  2184. break;
  2185. case PatternCursor::volumeColumn:
  2186. vsrc = srcCmd.vol;
  2187. vdest = destCmd.vol;
  2188. vcmd = srcCmd.volcmd;
  2189. verr = (distance * 63) / 128;
  2190. if(srcCmd.volcmd == VOLCMD_NONE)
  2191. {
  2192. vcmd = destCmd.volcmd;
  2193. if(vcmd == VOLCMD_VOLUME && srcCmd.IsNote() && srcCmd.instr)
  2194. vsrc = GetDefaultVolume(srcCmd);
  2195. else
  2196. vsrc = vdest;
  2197. } else if(destCmd.volcmd == VOLCMD_NONE)
  2198. {
  2199. if(vcmd == VOLCMD_VOLUME && destCmd.IsNote() && destCmd.instr)
  2200. vdest = GetDefaultVolume(srcCmd);
  2201. else
  2202. vdest = vsrc;
  2203. }
  2204. break;
  2205. case PatternCursor::paramColumn:
  2206. case PatternCursor::effectColumn:
  2207. if(srcCmd.IsPcNote() || destCmd.IsPcNote())
  2208. {
  2209. doPCinterpolation = true;
  2210. PCnote = (srcCmd.IsPcNote()) ? srcCmd.note : destCmd.note;
  2211. vsrc = srcCmd.GetValueEffectCol();
  2212. vdest = destCmd.GetValueEffectCol();
  2213. PCparam = srcCmd.GetValueVolCol();
  2214. if((PCparam == 0 && destCmd.IsPcNote()) || !srcCmd.IsPcNote())
  2215. PCparam = destCmd.GetValueVolCol();
  2216. PCinst = srcCmd.instr;
  2217. if(PCinst == 0)
  2218. PCinst = destCmd.instr;
  2219. } else
  2220. {
  2221. vsrc = srcCmd.param;
  2222. vdest = destCmd.param;
  2223. vcmd = srcCmd.command;
  2224. if(srcCmd.command == CMD_NONE)
  2225. {
  2226. vsrc = vdest;
  2227. vcmd = destCmd.command;
  2228. } else if(destCmd.command == CMD_NONE)
  2229. {
  2230. vdest = vsrc;
  2231. }
  2232. }
  2233. verr = (distance * 63) / 128;
  2234. break;
  2235. default:
  2236. MPT_ASSERT(false);
  2237. return;
  2238. }
  2239. if(vdest < vsrc)
  2240. verr = -verr;
  2241. ModCommand *pcmd = sndFile->Patterns[m_nPattern].GetpModCommand(row0, nchn);
  2242. for(int i = 0; i <= distance; i++, pcmd += sndFile->GetNumChannels())
  2243. {
  2244. switch(type)
  2245. {
  2246. case PatternCursor::noteColumn:
  2247. if((pcmd->note == NOTE_NONE || pcmd->instr == vcmd) && !pcmd->IsPcNote())
  2248. {
  2249. int note = vsrc + ((vdest - vsrc) * i + verr) / distance;
  2250. pcmd->note = static_cast<ModCommand::NOTE>(note);
  2251. if(pcmd->instr == 0)
  2252. pcmd->instr = static_cast<ModCommand::VOLCMD>(vcmd);
  2253. }
  2254. break;
  2255. case PatternCursor::instrColumn:
  2256. if(pcmd->instr == 0)
  2257. {
  2258. int instr = vsrc + ((vdest - vsrc) * i + verr) / distance;
  2259. pcmd->instr = static_cast<ModCommand::INSTR>(instr);
  2260. }
  2261. break;
  2262. case PatternCursor::volumeColumn:
  2263. if((pcmd->volcmd == VOLCMD_NONE || pcmd->volcmd == vcmd) && !pcmd->IsPcNote())
  2264. {
  2265. int vol = vsrc + ((vdest - vsrc) * i + verr) / distance;
  2266. pcmd->vol = static_cast<ModCommand::VOL>(vol);
  2267. pcmd->volcmd = static_cast<ModCommand::VOLCMD>(vcmd);
  2268. }
  2269. break;
  2270. case PatternCursor::effectColumn:
  2271. if(doPCinterpolation)
  2272. { // With PC/PCs notes, copy PCs note and plug index to all rows where
  2273. // effect interpolation is done if no PC note with non-zero instrument is there.
  2274. const uint16 val = static_cast<uint16>(vsrc + ((vdest - vsrc) * i + verr) / distance);
  2275. if(!pcmd->IsPcNote() || pcmd->instr == 0)
  2276. {
  2277. pcmd->note = PCnote;
  2278. pcmd->instr = static_cast<ModCommand::INSTR>(PCinst);
  2279. }
  2280. pcmd->SetValueVolCol(PCparam);
  2281. pcmd->SetValueEffectCol(val);
  2282. } else if(!pcmd->IsPcNote())
  2283. {
  2284. if((pcmd->command == CMD_NONE) || (pcmd->command == vcmd))
  2285. {
  2286. int val = vsrc + ((vdest - vsrc) * i + verr) / distance;
  2287. pcmd->param = static_cast<ModCommand::PARAM>(val);
  2288. pcmd->command = static_cast<ModCommand::COMMAND>(vcmd);
  2289. }
  2290. }
  2291. break;
  2292. default:
  2293. MPT_ASSERT(false);
  2294. }
  2295. }
  2296. changed = true;
  2297. } //end for all channels where type is selected
  2298. if(changed)
  2299. {
  2300. SetModified(false);
  2301. InvalidatePattern(false);
  2302. }
  2303. }
  2304. void CViewPattern::OnResetChannelColors()
  2305. {
  2306. CModDoc &modDoc = *GetDocument();
  2307. const CSoundFile &sndFile = *GetSoundFile();
  2308. modDoc.GetPatternUndo().PrepareChannelUndo(0, sndFile.GetNumChannels(), "Reset Channel Colours");
  2309. if(modDoc.SetDefaultChannelColors())
  2310. {
  2311. if(modDoc.SupportsChannelColors())
  2312. modDoc.SetModified();
  2313. modDoc.UpdateAllViews(nullptr, GeneralHint().Channels(), nullptr);
  2314. } else
  2315. {
  2316. modDoc.GetPatternUndo().RemoveLastUndoStep();
  2317. }
  2318. }
  2319. void CViewPattern::OnTransposeChannel()
  2320. {
  2321. CInputDlg dlg(this, _T("Enter transpose amount (affects all patterns):"), -(NOTE_MAX - NOTE_MIN), (NOTE_MAX - NOTE_MIN), m_nTransposeAmount);
  2322. if(dlg.DoModal() == IDOK)
  2323. {
  2324. m_nTransposeAmount = dlg.resultAsInt;
  2325. CSoundFile &sndFile = *GetSoundFile();
  2326. bool changed = false;
  2327. // Don't allow notes outside our supported note range.
  2328. const ModCommand::NOTE noteMin = sndFile.GetModSpecifications().noteMin;
  2329. const ModCommand::NOTE noteMax = sndFile.GetModSpecifications().noteMax;
  2330. for(PATTERNINDEX pat = 0; pat < sndFile.Patterns.Size(); pat++)
  2331. {
  2332. bool changedThisPat = false;
  2333. if(sndFile.Patterns.IsValidPat(pat))
  2334. {
  2335. ModCommand *m = sndFile.Patterns[pat].GetpModCommand(0, m_MenuCursor.GetChannel());
  2336. const ROWINDEX numRows = sndFile.Patterns[pat].GetNumRows();
  2337. for(ROWINDEX row = 0; row < numRows; row++)
  2338. {
  2339. if(m->IsNote())
  2340. {
  2341. if(!changedThisPat)
  2342. {
  2343. GetDocument()->GetPatternUndo().PrepareUndo(pat, m_MenuCursor.GetChannel(), 0, 1, numRows, "Transpose Channel", changed);
  2344. changed = changedThisPat = true;
  2345. }
  2346. int note = m->note + m_nTransposeAmount;
  2347. Limit(note, noteMin, noteMax);
  2348. m->note = static_cast<ModCommand::NOTE>(note);
  2349. }
  2350. m += sndFile.Patterns[pat].GetNumChannels();
  2351. }
  2352. }
  2353. }
  2354. if(changed)
  2355. {
  2356. SetModified(true);
  2357. InvalidatePattern(false);
  2358. }
  2359. }
  2360. }
  2361. void CViewPattern::OnTransposeCustom()
  2362. {
  2363. CInputDlg dlg(this, _T("Enter transpose amount:"), -(NOTE_MAX - NOTE_MIN), (NOTE_MAX - NOTE_MIN), m_nTransposeAmount);
  2364. if(dlg.DoModal() == IDOK)
  2365. {
  2366. m_nTransposeAmount = dlg.resultAsInt;
  2367. TransposeSelection(dlg.resultAsInt);
  2368. }
  2369. }
  2370. void CViewPattern::OnTransposeCustomQuick()
  2371. {
  2372. if(m_nTransposeAmount != 0)
  2373. TransposeSelection(m_nTransposeAmount);
  2374. else
  2375. OnTransposeCustom();
  2376. }
  2377. bool CViewPattern::TransposeSelection(int transp)
  2378. {
  2379. CSoundFile *pSndFile = GetSoundFile();
  2380. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  2381. {
  2382. return false;
  2383. }
  2384. m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
  2385. // Don't allow notes outside our supported note range.
  2386. const ModCommand::NOTE noteMin = pSndFile->GetModSpecifications().noteMin;
  2387. const ModCommand::NOTE noteMax = pSndFile->GetModSpecifications().noteMax;
  2388. PrepareUndo(m_Selection, "Transpose");
  2389. std::vector<int> lastGroupSize(pSndFile->GetNumChannels(), 12);
  2390. ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
  2391. {
  2392. if(chn == m_Selection.GetStartChannel() && m_Selection.GetStartColumn() > PatternCursor::noteColumn)
  2393. return;
  2394. if(m.IsNote())
  2395. {
  2396. if(m.instr > 0)
  2397. {
  2398. lastGroupSize[chn] = GetDocument()->GetInstrumentGroupSize(m.instr);
  2399. }
  2400. int transpose = transp;
  2401. if(transpose == 12000 || transpose == -12000)
  2402. {
  2403. // Transpose one octave
  2404. transpose = lastGroupSize[chn] * mpt::signum(transpose);
  2405. }
  2406. int note = m.note + transpose;
  2407. Limit(note, noteMin, noteMax);
  2408. m.note = static_cast<ModCommand::NOTE>(note);
  2409. }
  2410. });
  2411. SetModified(false);
  2412. InvalidateSelection();
  2413. if(m_Selection.GetNumChannels() == 1 && m_Selection.GetNumRows() == 1 && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYTRANSPOSE))
  2414. {
  2415. // Preview a single transposed note
  2416. PreviewNote(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
  2417. }
  2418. return true;
  2419. }
  2420. bool CViewPattern::DataEntry(bool up, bool coarse)
  2421. {
  2422. CSoundFile *pSndFile = GetSoundFile();
  2423. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  2424. {
  2425. return false;
  2426. }
  2427. m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
  2428. const PatternCursor::Columns column = m_Selection.GetStartColumn();
  2429. // Don't allow notes outside our supported note range.
  2430. const ModCommand::NOTE noteMin = pSndFile->GetModSpecifications().noteMin;
  2431. const ModCommand::NOTE noteMax = pSndFile->GetModSpecifications().noteMax;
  2432. const int instrMax = std::min(static_cast<int>(Util::MaxValueOfType(ModCommand::INSTR())), static_cast<int>(pSndFile->GetNumInstruments() ? pSndFile->GetNumInstruments() : pSndFile->GetNumSamples()));
  2433. const EffectInfo effectInfo(*pSndFile);
  2434. const int offset = up ? 1 : -1;
  2435. PrepareUndo(m_Selection, "Data Entry");
  2436. // Notes per octave for non-TET12 tunings and coarse note steps
  2437. std::vector<int> lastGroupSize(pSndFile->GetNumChannels(), 12);
  2438. bool applyToSpecialNotes = true;
  2439. if(column == PatternCursor::noteColumn)
  2440. {
  2441. const CPattern &pattern = pSndFile->Patterns[m_nPattern];
  2442. const CHANNELINDEX startChn = m_Selection.GetStartChannel(), endChn = m_Selection.GetEndChannel();
  2443. const ROWINDEX endRow = m_Selection.GetEndRow();
  2444. for(ROWINDEX row = m_Selection.GetStartRow(); row <= endRow && applyToSpecialNotes; row++)
  2445. {
  2446. const ModCommand *m = pattern.GetpModCommand(row, startChn);
  2447. for(CHANNELINDEX chn = startChn; chn <= endChn; chn++, m++)
  2448. {
  2449. if(!m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::noteColumn)))
  2450. continue;
  2451. if(m->IsNote())
  2452. {
  2453. applyToSpecialNotes = false;
  2454. break;
  2455. }
  2456. }
  2457. }
  2458. }
  2459. ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
  2460. {
  2461. if(column == PatternCursor::noteColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::noteColumn)))
  2462. {
  2463. // Increase / decrease note
  2464. if(m.IsNote() && !applyToSpecialNotes)
  2465. {
  2466. if(m.instr > 0)
  2467. {
  2468. lastGroupSize[chn] = GetDocument()->GetInstrumentGroupSize(m.instr);
  2469. }
  2470. int note = m.note + offset * (coarse ? lastGroupSize[chn] : 1);
  2471. Limit(note, noteMin, noteMax);
  2472. m.note = (ModCommand::NOTE)note;
  2473. } else if(m.IsSpecialNote() && applyToSpecialNotes)
  2474. {
  2475. ModCommand::NOTE note = m.note;
  2476. do
  2477. {
  2478. note = static_cast<ModCommand::NOTE>(note + offset);
  2479. if(!ModCommand::IsSpecialNote(note))
  2480. {
  2481. break;
  2482. }
  2483. } while(!pSndFile->GetModSpecifications().HasNote(note));
  2484. if(ModCommand::IsSpecialNote(note))
  2485. {
  2486. if(m.IsPcNote() != ModCommand::IsPcNote(note))
  2487. {
  2488. m.Clear();
  2489. }
  2490. m.note = (ModCommand::NOTE)note;
  2491. }
  2492. }
  2493. }
  2494. if(column == PatternCursor::instrColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::instrColumn)) && m.instr != 0)
  2495. {
  2496. // Increase / decrease instrument
  2497. int instr = m.instr + offset * (coarse ? 10 : 1);
  2498. Limit(instr, 1, m.IsInstrPlug() ? MAX_MIXPLUGINS : instrMax);
  2499. m.instr = (ModCommand::INSTR)instr;
  2500. }
  2501. if(column == PatternCursor::volumeColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::volumeColumn)))
  2502. {
  2503. // Increase / decrease volume parameter
  2504. if(m.IsPcNote())
  2505. {
  2506. int val = m.GetValueVolCol() + offset * (coarse ? 10 : 1);
  2507. Limit(val, 0, ModCommand::maxColumnValue);
  2508. m.SetValueVolCol(static_cast<uint16>(val));
  2509. } else
  2510. {
  2511. int vol = m.vol + offset * (coarse ? 10 : 1);
  2512. if(m.volcmd == VOLCMD_NONE && m.IsNote() && m.instr)
  2513. {
  2514. m.volcmd = VOLCMD_VOLUME;
  2515. vol = GetDefaultVolume(m);
  2516. }
  2517. ModCommand::VOL minValue = 0, maxValue = 64;
  2518. effectInfo.GetVolCmdInfo(effectInfo.GetIndexFromVolCmd(m.volcmd), nullptr, &minValue, &maxValue);
  2519. Limit(vol, (int)minValue, (int)maxValue);
  2520. m.vol = (ModCommand::VOL)vol;
  2521. }
  2522. }
  2523. if((column == PatternCursor::effectColumn || column == PatternCursor::paramColumn) && (m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::effectColumn)) || m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::paramColumn))))
  2524. {
  2525. // Increase / decrease effect parameter
  2526. if(m.IsPcNote())
  2527. {
  2528. int val = m.GetValueEffectCol() + offset * (coarse ? 10 : 1);
  2529. Limit(val, 0, ModCommand::maxColumnValue);
  2530. m.SetValueEffectCol(static_cast<uint16>(val));
  2531. } else
  2532. {
  2533. int param = m.param + offset * (coarse ? 16 : 1);
  2534. ModCommand::PARAM minValue = 0x00, maxValue = 0xFF;
  2535. if(!m.IsSlideUpDownCommand())
  2536. {
  2537. const auto effectIndex = effectInfo.GetIndexFromEffect(m.command, m.param);
  2538. effectInfo.GetEffectInfo(effectIndex, nullptr, false, &minValue, &maxValue);
  2539. minValue = static_cast<ModCommand::PARAM>(effectInfo.MapPosToValue(effectIndex, minValue));
  2540. maxValue = static_cast<ModCommand::PARAM>(effectInfo.MapPosToValue(effectIndex, maxValue));
  2541. }
  2542. m.param = static_cast<ModCommand::PARAM>(Clamp(param, minValue, maxValue));
  2543. }
  2544. }
  2545. });
  2546. SetModified(false);
  2547. InvalidatePattern();
  2548. if(column == PatternCursor::noteColumn && m_Selection.GetNumChannels() == 1 && m_Selection.GetNumRows() == 1 && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYTRANSPOSE))
  2549. {
  2550. // Preview a single transposed note
  2551. PreviewNote(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
  2552. }
  2553. return true;
  2554. }
  2555. // Get the velocity at which a given note would be played
  2556. int CViewPattern::GetDefaultVolume(const ModCommand &m, ModCommand::INSTR lastInstr) const
  2557. {
  2558. const CSoundFile &sndFile = *GetSoundFile();
  2559. SAMPLEINDEX sample = GetDocument()->GetSampleIndex(m, lastInstr);
  2560. if(sample)
  2561. return std::min(sndFile.GetSample(sample).nVolume, uint16(256)) / 4u;
  2562. else if(m.instr > 0 && m.instr <= sndFile.GetNumInstruments() && sndFile.Instruments[m.instr] != nullptr && sndFile.Instruments[m.instr]->HasValidMIDIChannel())
  2563. return std::min(sndFile.Instruments[m.instr]->nGlobalVol, uint32(64)); // For instrument plugins
  2564. else
  2565. return 64;
  2566. }
  2567. int CViewPattern::GetBaseNote() const
  2568. {
  2569. const CModDoc *modDoc = GetDocument();
  2570. INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
  2571. if(!instr && !IsLiveRecord())
  2572. instr = GetCursorCommand().instr;
  2573. return modDoc->GetBaseNote(instr);
  2574. }
  2575. ModCommand::NOTE CViewPattern::GetNoteWithBaseOctave(int note) const
  2576. {
  2577. const CModDoc *modDoc = GetDocument();
  2578. INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
  2579. if(!instr && !IsLiveRecord())
  2580. instr = GetCursorCommand().instr;
  2581. return modDoc->GetNoteWithBaseOctave(note, instr);
  2582. }
  2583. void CViewPattern::OnDropSelection()
  2584. {
  2585. CModDoc *pModDoc;
  2586. if((pModDoc = GetDocument()) == nullptr || !IsEditingEnabled_bmsg())
  2587. {
  2588. return;
  2589. }
  2590. CSoundFile &sndFile = pModDoc->GetSoundFile();
  2591. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  2592. {
  2593. return;
  2594. }
  2595. // Compute relative movement
  2596. int dx = (int)m_DragPos.GetChannel() - (int)m_StartSel.GetChannel();
  2597. int dy = (int)m_DragPos.GetRow() - (int)m_StartSel.GetRow();
  2598. if((!dx) && (!dy))
  2599. {
  2600. return;
  2601. }
  2602. // Allocate replacement pattern
  2603. CPattern &pattern = sndFile.Patterns[m_nPattern];
  2604. auto origPattern = pattern.GetData();
  2605. // Compute destination rect
  2606. PatternCursor begin(m_Selection.GetUpperLeft()), end(m_Selection.GetLowerRight());
  2607. begin.Move(dy, dx, 0);
  2608. if(begin.GetChannel() >= sndFile.GetNumChannels())
  2609. {
  2610. // Moved outside pattern range.
  2611. return;
  2612. }
  2613. end.Move(dy, dx, 0);
  2614. if(end.GetColumnType() == PatternCursor::effectColumn)
  2615. {
  2616. // Extend to parameter column
  2617. end.Move(0, 0, 1);
  2618. }
  2619. begin.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
  2620. end.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
  2621. PatternRect destination(begin, end);
  2622. const bool moveSelection = !m_Status[psKeyboardDragSelect | psCtrlDragSelect];
  2623. BeginWaitCursor();
  2624. pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, 0, 0, sndFile.GetNumChannels(), pattern.GetNumRows(), moveSelection ? "Move Selection" : "Copy Selection");
  2625. const ModCommand empty = ModCommand::Empty();
  2626. auto p = pattern.begin();
  2627. for(ROWINDEX row = 0; row < pattern.GetNumRows(); row++)
  2628. {
  2629. for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++, p++)
  2630. {
  2631. for(int c = PatternCursor::firstColumn; c <= PatternCursor::lastColumn; c++)
  2632. {
  2633. PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(c));
  2634. int xsrc = chn, ysrc = row;
  2635. if(destination.Contains(cell))
  2636. {
  2637. // Current cell is from destination selection
  2638. xsrc -= dx;
  2639. ysrc -= dy;
  2640. } else if(m_Selection.Contains(cell))
  2641. {
  2642. // Current cell is from source rectangle (clear)
  2643. if(moveSelection)
  2644. {
  2645. xsrc = -1;
  2646. }
  2647. } else
  2648. {
  2649. continue;
  2650. }
  2651. // Copy the data
  2652. const ModCommand &src = (xsrc >= 0 && xsrc < (int)sndFile.GetNumChannels() && ysrc >= 0 && ysrc < (int)sndFile.Patterns[m_nPattern].GetNumRows()) ? origPattern[ysrc * sndFile.GetNumChannels() + xsrc] : empty;
  2653. switch(c)
  2654. {
  2655. case PatternCursor::noteColumn:
  2656. p->note = src.note;
  2657. break;
  2658. case PatternCursor::instrColumn:
  2659. p->instr = src.instr;
  2660. break;
  2661. case PatternCursor::volumeColumn:
  2662. p->vol = src.vol;
  2663. p->volcmd = src.volcmd;
  2664. break;
  2665. case PatternCursor::effectColumn:
  2666. p->command = src.command;
  2667. p->param = src.param;
  2668. break;
  2669. }
  2670. }
  2671. }
  2672. }
  2673. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  2674. SetCursorPosition(begin);
  2675. SetCurSel(destination);
  2676. InvalidatePattern();
  2677. SetModified(false);
  2678. EndWaitCursor();
  2679. }
  2680. void CViewPattern::OnSetSelInstrument()
  2681. {
  2682. SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(GetCurrentInstrument()), false);
  2683. }
  2684. void CViewPattern::OnRemoveChannelDialog()
  2685. {
  2686. CModDoc *pModDoc = GetDocument();
  2687. if(pModDoc == nullptr)
  2688. return;
  2689. pModDoc->ChangeNumChannels(0);
  2690. SetCurrentPattern(m_nPattern); //Updating the screen.
  2691. }
  2692. void CViewPattern::OnRemoveChannel()
  2693. {
  2694. CModDoc *pModDoc = GetDocument();
  2695. if(pModDoc == nullptr)
  2696. return;
  2697. const CSoundFile &sndFile = pModDoc->GetSoundFile();
  2698. if(sndFile.GetNumChannels() <= sndFile.GetModSpecifications().channelsMin)
  2699. {
  2700. Reporting::Error("No channel removed - channel number already at minimum.", "Remove channel");
  2701. return;
  2702. }
  2703. CHANNELINDEX nChn = m_MenuCursor.GetChannel();
  2704. const bool isEmpty = pModDoc->IsChannelUnused(nChn);
  2705. CString str;
  2706. str.Format(_T("Remove channel %d? This channel still contains note data!"), nChn + 1);
  2707. if(isEmpty || Reporting::Confirm(str, "Remove channel") == cnfYes)
  2708. {
  2709. std::vector<bool> keepMask(pModDoc->GetNumChannels(), true);
  2710. keepMask[nChn] = false;
  2711. pModDoc->RemoveChannels(keepMask, true);
  2712. SetCurrentPattern(m_nPattern); //Updating the screen.
  2713. pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this);
  2714. }
  2715. }
  2716. void CViewPattern::AddChannel(CHANNELINDEX parent, bool afterCurrent)
  2717. {
  2718. CModDoc *pModDoc = GetDocument();
  2719. if(pModDoc == nullptr)
  2720. return;
  2721. BeginWaitCursor();
  2722. // Create new channel order, with channel nBefore being an invalid (and thus empty) channel.
  2723. std::vector<CHANNELINDEX> channels(pModDoc->GetNumChannels() + 1, CHANNELINDEX_INVALID);
  2724. CHANNELINDEX i = 0;
  2725. for(CHANNELINDEX nChn = 0; nChn < pModDoc->GetNumChannels() + 1; nChn++)
  2726. {
  2727. if(nChn != (parent + (afterCurrent ? 1 : 0)))
  2728. {
  2729. channels[nChn] = i++;
  2730. }
  2731. }
  2732. if(pModDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
  2733. {
  2734. auto &chnSettings = pModDoc->GetSoundFile().ChnSettings;
  2735. chnSettings[parent + (afterCurrent ? 1 : 0)].color = chnSettings[parent + (afterCurrent ? 0 : 1)].color;
  2736. pModDoc->SetModified();
  2737. pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this); //refresh channel headers
  2738. SetCurrentPattern(m_nPattern);
  2739. }
  2740. EndWaitCursor();
  2741. }
  2742. void CViewPattern::OnDuplicateChannel()
  2743. {
  2744. CModDoc *pModDoc = GetDocument();
  2745. if(pModDoc == nullptr)
  2746. return;
  2747. const CHANNELINDEX dupChn = m_MenuCursor.GetChannel();
  2748. if(dupChn >= pModDoc->GetNumChannels())
  2749. return;
  2750. if(!pModDoc->IsChannelUnused(dupChn) && Reporting::Confirm(_T("This affects all patterns, proceed?"), _T("Duplicate Channel")) != cnfYes)
  2751. return;
  2752. BeginWaitCursor();
  2753. // Create new channel order, with channel nDupChn duplicated.
  2754. std::vector<CHANNELINDEX> channels(pModDoc->GetNumChannels() + 1, 0);
  2755. CHANNELINDEX i = 0;
  2756. for(CHANNELINDEX nChn = 0; nChn < pModDoc->GetNumChannels() + 1; nChn++)
  2757. {
  2758. channels[nChn] = i;
  2759. if(nChn != dupChn)
  2760. i++;
  2761. }
  2762. // Check that duplication happened and in that case update.
  2763. if(pModDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
  2764. {
  2765. pModDoc->SetModified();
  2766. pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this); //refresh channel headers
  2767. SetCurrentPattern(m_nPattern);
  2768. }
  2769. EndWaitCursor();
  2770. }
  2771. void CViewPattern::OnRunScript()
  2772. {
  2773. ;
  2774. }
  2775. void CViewPattern::OnSwitchToOrderList()
  2776. {
  2777. PostCtrlMessage(CTRLMSG_SETFOCUS);
  2778. }
  2779. void CViewPattern::OnPrevOrder()
  2780. {
  2781. PostCtrlMessage(CTRLMSG_PREVORDER);
  2782. }
  2783. void CViewPattern::OnNextOrder()
  2784. {
  2785. PostCtrlMessage(CTRLMSG_NEXTORDER);
  2786. }
  2787. void CViewPattern::OnUpdateUndo(CCmdUI *pCmdUI)
  2788. {
  2789. CModDoc *pModDoc = GetDocument();
  2790. if((pCmdUI) && (pModDoc))
  2791. {
  2792. pCmdUI->Enable(pModDoc->GetPatternUndo().CanUndo());
  2793. pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditUndo, _T("Undo ") + pModDoc->GetPatternUndo().GetUndoName()));
  2794. }
  2795. }
  2796. void CViewPattern::OnUpdateRedo(CCmdUI *pCmdUI)
  2797. {
  2798. CModDoc *pModDoc = GetDocument();
  2799. if((pCmdUI) && (pModDoc))
  2800. {
  2801. pCmdUI->Enable(pModDoc->GetPatternUndo().CanRedo());
  2802. pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditRedo, _T("Redo ") + pModDoc->GetPatternUndo().GetRedoName()));
  2803. }
  2804. }
  2805. void CViewPattern::OnEditUndo()
  2806. {
  2807. UndoRedo(true);
  2808. }
  2809. void CViewPattern::OnEditRedo()
  2810. {
  2811. UndoRedo(false);
  2812. }
  2813. void CViewPattern::UndoRedo(bool undo)
  2814. {
  2815. CModDoc *pModDoc = GetDocument();
  2816. if(pModDoc && IsEditingEnabled_bmsg())
  2817. {
  2818. CHANNELINDEX oldNumChannels = pModDoc->GetNumChannels();
  2819. PATTERNINDEX pat = undo ? pModDoc->GetPatternUndo().Undo() : pModDoc->GetPatternUndo().Redo();
  2820. const CSoundFile &sndFile = pModDoc->GetSoundFile();
  2821. if(pat < sndFile.Patterns.Size())
  2822. {
  2823. if(pat != m_nPattern)
  2824. {
  2825. // Find pattern in sequence.
  2826. ORDERINDEX matchingOrder = Order().FindOrder(pat, GetCurrentOrder());
  2827. if(matchingOrder != ORDERINDEX_INVALID)
  2828. {
  2829. SetCurrentOrder(matchingOrder);
  2830. }
  2831. SetCurrentPattern(pat);
  2832. } else
  2833. {
  2834. InvalidatePattern(true, true);
  2835. }
  2836. SetModified(false);
  2837. SanitizeCursor();
  2838. UpdateScrollSize();
  2839. }
  2840. if(oldNumChannels != pModDoc->GetNumChannels())
  2841. {
  2842. pModDoc->UpdateAllViews(this, GeneralHint().Channels().ModType(), this);
  2843. }
  2844. }
  2845. }
  2846. // Apply amplification and fade function to volume
  2847. static void AmplifyFade(int &vol, int amp, ROWINDEX row, ROWINDEX numRows, int fadeIn, int fadeOut, Fade::Func &fadeFunc)
  2848. {
  2849. const bool doFadeIn = fadeIn != amp, doFadeOut = fadeOut != amp;
  2850. const double fadeStart = fadeIn / 100.0, fadeStartDiff = (amp - fadeIn) / 100.0;
  2851. const double fadeEnd = fadeOut / 100.0, fadeEndDiff = (amp - fadeOut) / 100.0;
  2852. double l;
  2853. if(doFadeIn && doFadeOut)
  2854. {
  2855. ROWINDEX numRows2 = numRows / 2;
  2856. if(row < numRows2)
  2857. l = fadeStart + fadeFunc(static_cast<double>(row) / numRows2) * fadeStartDiff;
  2858. else
  2859. l = fadeEnd + fadeFunc(static_cast<double>(numRows - row) / (numRows - numRows2)) * fadeEndDiff;
  2860. } else if(doFadeIn)
  2861. {
  2862. l = fadeStart + fadeFunc(static_cast<double>(row + 1) / numRows) * fadeStartDiff;
  2863. } else if(doFadeOut)
  2864. {
  2865. l = fadeEnd + fadeFunc(static_cast<double>(numRows - row) / numRows) * fadeEndDiff;
  2866. } else
  2867. {
  2868. l = amp / 100.0;
  2869. }
  2870. vol = mpt::saturate_round<int>(vol * l);
  2871. Limit(vol, 0, 64);
  2872. }
  2873. void CViewPattern::OnPatternAmplify()
  2874. {
  2875. static CAmpDlg::AmpSettings settings{Fade::kLinear, 0, 0, 100, false, false};
  2876. CAmpDlg dlg(this, settings, 0);
  2877. if(dlg.DoModal() != IDOK)
  2878. {
  2879. return;
  2880. }
  2881. CSoundFile &sndFile = *GetSoundFile();
  2882. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  2883. return;
  2884. const bool useVolCol = sndFile.GetModSpecifications().HasVolCommand(VOLCMD_VOLUME);
  2885. BeginWaitCursor();
  2886. PrepareUndo(m_Selection, "Amplify");
  2887. m_Selection.Sanitize(sndFile.Patterns[m_nPattern].GetNumRows(), sndFile.GetNumChannels());
  2888. const CHANNELINDEX firstChannel = m_Selection.GetStartChannel(), lastChannel = m_Selection.GetEndChannel();
  2889. const ROWINDEX firstRow = m_Selection.GetStartRow(), lastRow = m_Selection.GetEndRow();
  2890. // For partically selected start and end channels, we check if the start and end columns contain the relevant columns.
  2891. bool firstChannelValid, lastChannelValid;
  2892. if(useVolCol)
  2893. {
  2894. // Volume column
  2895. firstChannelValid = m_Selection.ContainsHorizontal(PatternCursor(0, firstChannel, PatternCursor::volumeColumn));
  2896. lastChannelValid = m_Selection.ContainsHorizontal(PatternCursor(0, lastChannel, PatternCursor::volumeColumn));
  2897. } else
  2898. {
  2899. // Effect column
  2900. firstChannelValid = true; // We cannot start "too far right" in the channel, since this is the last column.
  2901. lastChannelValid = m_Selection.GetLowerRight().CompareColumn(PatternCursor(0, lastChannel, PatternCursor::effectColumn)) >= 0;
  2902. }
  2903. // Adjust min/max channel if they're only partly selected (i.e. volume column or effect column (when using .MOD) is not covered)
  2904. // XXX if only the effect column is marked in the XM format, we cannot amplify volume commands there. Does anyone use that?
  2905. if((!firstChannelValid && firstChannel >= lastChannel) || (!lastChannelValid && lastChannel <= firstChannel))
  2906. {
  2907. EndWaitCursor();
  2908. return;
  2909. }
  2910. // Volume memory for each channel.
  2911. std::vector<ModCommand::VOL> chvol(lastChannel + 1, 64);
  2912. // First, fill the volume memory in case we start the selection before some note
  2913. ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
  2914. {
  2915. if((chn == firstChannel && !firstChannelValid) || (chn == lastChannel && !lastChannelValid))
  2916. return;
  2917. if(m.command == CMD_VOLUME)
  2918. chvol[chn] = std::min(m.param, ModCommand::PARAM(64));
  2919. else if(m.volcmd == VOLCMD_VOLUME)
  2920. chvol[chn] = m.vol;
  2921. else if(m.instr != 0)
  2922. chvol[chn] = static_cast<ModCommand::VOL>(GetDefaultVolume(m));
  2923. });
  2924. Fade::Func fadeFunc = GetFadeFunc(settings.fadeLaw);
  2925. // Now do the actual amplification
  2926. const int cy = lastRow - firstRow + 1; // total rows (for fading)
  2927. ApplyToSelection([&] (ModCommand &m, ROWINDEX nRow, CHANNELINDEX chn)
  2928. {
  2929. if((chn == firstChannel && !firstChannelValid) || (chn == lastChannel && !lastChannelValid))
  2930. return;
  2931. if(m.command == CMD_VOLUME)
  2932. chvol[chn] = std::min(m.param, ModCommand::PARAM(64));
  2933. else if(m.volcmd == VOLCMD_VOLUME)
  2934. chvol[chn] = m.vol;
  2935. else if(m.instr != 0)
  2936. chvol[chn] = static_cast<ModCommand::VOL>(GetDefaultVolume(m));
  2937. if(settings.fadeIn || settings.fadeOut || (m.IsNote() && m.instr != 0))
  2938. {
  2939. // Insert new volume commands where necessary
  2940. if(useVolCol && m.volcmd == VOLCMD_NONE)
  2941. {
  2942. m.volcmd = VOLCMD_VOLUME;
  2943. m.vol = chvol[chn];
  2944. } else if(!useVolCol && m.command == CMD_NONE)
  2945. {
  2946. m.command = CMD_VOLUME;
  2947. m.param = chvol[chn];
  2948. }
  2949. }
  2950. if(m.volcmd == VOLCMD_VOLUME)
  2951. {
  2952. int vol = m.vol;
  2953. AmplifyFade(vol, settings.factor, nRow - firstRow, cy, settings.fadeIn ? settings.fadeInStart : settings.factor, settings.fadeOut ? settings.fadeOutEnd : settings.factor, fadeFunc);
  2954. m.vol = static_cast<ModCommand::VOL>(vol);
  2955. }
  2956. if(m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::effectColumn)) || m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::paramColumn)))
  2957. {
  2958. if(m.command == CMD_VOLUME && m.param <= 64)
  2959. {
  2960. int vol = m.param;
  2961. AmplifyFade(vol, settings.factor, nRow - firstRow, cy, settings.fadeIn ? settings.fadeInStart : settings.factor, settings.fadeOut ? settings.fadeOutEnd : settings.factor, fadeFunc);
  2962. m.param = static_cast<ModCommand::PARAM>(vol);
  2963. }
  2964. }
  2965. });
  2966. SetModified(false);
  2967. InvalidateSelection();
  2968. EndWaitCursor();
  2969. }
  2970. LRESULT CViewPattern::OnPlayerNotify(Notification *pnotify)
  2971. {
  2972. CSoundFile *pSndFile = GetSoundFile();
  2973. if(pSndFile == nullptr || pnotify == nullptr)
  2974. {
  2975. return 0;
  2976. }
  2977. if(pnotify->type[Notification::Position])
  2978. {
  2979. ORDERINDEX ord = pnotify->order;
  2980. ROWINDEX row = pnotify->row;
  2981. PATTERNINDEX pat = pnotify->pattern;
  2982. bool updateOrderList = false;
  2983. if(m_nLastPlayedOrder != ord)
  2984. {
  2985. updateOrderList = true;
  2986. m_nLastPlayedOrder = ord;
  2987. }
  2988. if(row < m_nLastPlayedRow)
  2989. {
  2990. InvalidateChannelsHeaders();
  2991. }
  2992. m_nLastPlayedRow = row;
  2993. if(!pSndFile->m_SongFlags[SONG_PAUSED | SONG_STEP])
  2994. {
  2995. const auto &order = Order();
  2996. if(ord >= order.GetLength() || order[ord] != pat)
  2997. {
  2998. //order doesn't correlate with pattern, so mark it as invalid
  2999. ord = ORDERINDEX_INVALID;
  3000. }
  3001. if(m_pEffectVis && m_pEffectVis->m_hWnd)
  3002. {
  3003. m_pEffectVis->SetPlayCursor(pat, row);
  3004. }
  3005. // Simple detection of backwards-going patterns to avoid jerky animation
  3006. m_nNextPlayRow = ROWINDEX_INVALID;
  3007. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SMOOTHSCROLL) && pSndFile->Patterns.IsValidPat(pat) && pSndFile->Patterns[pat].IsValidRow(row))
  3008. {
  3009. for(const ModCommand *m = pSndFile->Patterns[pat].GetRow(row), *mEnd = m + pSndFile->GetNumChannels(); m != mEnd; m++)
  3010. {
  3011. if(m->command == CMD_PATTERNBREAK)
  3012. m_nNextPlayRow = m->param;
  3013. else if(m->command == CMD_POSITIONJUMP && (m_nNextPlayRow == ROWINDEX_INVALID || pSndFile->GetType() == MOD_TYPE_XM))
  3014. m_nNextPlayRow = 0;
  3015. }
  3016. }
  3017. if(m_nNextPlayRow == ROWINDEX_INVALID)
  3018. m_nNextPlayRow = row + 1;
  3019. m_nTicksOnRow = pnotify->ticksOnRow;
  3020. SetPlayCursor(pat, row, pnotify->tick);
  3021. // Don't follow song if user drags selections or scrollbars.
  3022. if((m_Status & (psFollowSong | psDragActive)) == psFollowSong)
  3023. {
  3024. if(pat < pSndFile->Patterns.Size())
  3025. {
  3026. if(pat != m_nPattern || ord != m_nOrder || updateOrderList)
  3027. {
  3028. if(pat != m_nPattern)
  3029. SetCurrentPattern(pat, row);
  3030. else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SHOWPREVIOUS)
  3031. InvalidatePattern(true, true); // Redraw previous / next pattern
  3032. if(ord < order.GetLength())
  3033. {
  3034. m_nOrder = ord;
  3035. SendCtrlMessage(CTRLMSG_NOTIFYCURRENTORDER, ord);
  3036. }
  3037. updateOrderList = false;
  3038. }
  3039. if(row != GetCurrentRow())
  3040. {
  3041. SetCurrentRow((row < pSndFile->Patterns[pat].GetNumRows()) ? row : 0, false, false);
  3042. }
  3043. }
  3044. } else
  3045. {
  3046. if(updateOrderList)
  3047. {
  3048. SendCtrlMessage(CTRLMSG_FORCEREFRESH); //force orderlist refresh
  3049. updateOrderList = false;
  3050. }
  3051. }
  3052. }
  3053. }
  3054. if(pnotify->type[Notification::VUMeters | Notification::Stop] && m_Status[psShowVUMeters])
  3055. {
  3056. UpdateAllVUMeters(pnotify);
  3057. }
  3058. if(pnotify->type[Notification::Stop])
  3059. {
  3060. m_baPlayingNote.reset();
  3061. ChnVUMeters.fill(0); // Also zero all non-visible VU meters
  3062. SetPlayCursor(PATTERNINDEX_INVALID, ROWINDEX_INVALID, 0);
  3063. }
  3064. UpdateIndicator(false);
  3065. return 0;
  3066. }
  3067. // record plugin parameter changes into current pattern
  3068. LRESULT CViewPattern::OnRecordPlugParamChange(WPARAM plugSlot, LPARAM paramIndex)
  3069. {
  3070. CModDoc *pModDoc = GetDocument();
  3071. if(pModDoc == nullptr || !IsEditingEnabled())
  3072. return 0;
  3073. CSoundFile &sndFile = pModDoc->GetSoundFile();
  3074. //Work out where to put the new data
  3075. const PatternEditPos editPos = GetEditPos(sndFile, IsLiveRecord());
  3076. const CHANNELINDEX chn = editPos.channel;
  3077. const ROWINDEX row = editPos.row;
  3078. const PATTERNINDEX pattern = editPos.pattern;
  3079. ModCommand &mSrc = *sndFile.Patterns[pattern].GetpModCommand(row, chn);
  3080. ModCommand m = mSrc;
  3081. // TODO: Is the right plugin active? Move to a chan with the right plug
  3082. // Probably won't do this - finish fluctuator implementation instead.
  3083. IMixPlugin *pPlug = sndFile.m_MixPlugins[plugSlot].pMixPlugin;
  3084. if(pPlug == nullptr)
  3085. return 0;
  3086. if(sndFile.GetModSpecifications().HasNote(NOTE_PCS))
  3087. {
  3088. // MPTM: Use PC Notes
  3089. // only overwrite existing PC Notes
  3090. if(m.IsEmpty() || m.IsPcNote())
  3091. {
  3092. m.Set(NOTE_PCS, static_cast<ModCommand::INSTR>(plugSlot + 1), static_cast<uint16>(paramIndex), static_cast<uint16>(pPlug->GetParameter(static_cast<PlugParamIndex>(paramIndex)) * ModCommand::maxColumnValue));
  3093. }
  3094. } else if(sndFile.GetModSpecifications().HasCommand(CMD_SMOOTHMIDI))
  3095. {
  3096. // Other formats: Use MIDI macros
  3097. // Figure out which plug param (if any) is controllable using the active macro on this channel.
  3098. int activePlugParam = -1;
  3099. auto activeMacro = sndFile.m_PlayState.Chn[chn].nActiveMacro;
  3100. if(sndFile.m_MidiCfg.GetParameteredMacroType(activeMacro) == kSFxPlugParam)
  3101. activePlugParam = sndFile.m_MidiCfg.MacroToPlugParam(activeMacro);
  3102. // If the wrong macro is active, see if we can find the right one.
  3103. // If we can, activate it for this chan by writing appropriate SFx command it.
  3104. if(activePlugParam != paramIndex)
  3105. {
  3106. int foundMacro = sndFile.m_MidiCfg.FindMacroForParam(static_cast<PlugParamIndex>(paramIndex));
  3107. if(foundMacro >= 0)
  3108. {
  3109. sndFile.m_PlayState.Chn[chn].nActiveMacro = static_cast<uint8>(foundMacro);
  3110. if(m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI) //we overwrite existing Zxx and \xx only.
  3111. {
  3112. m.command = CMD_S3MCMDEX;
  3113. if(!sndFile.GetModSpecifications().HasCommand(CMD_S3MCMDEX))
  3114. m.command = CMD_MODCMDEX;
  3115. m.param = 0xF0 | (foundMacro & 0x0F);
  3116. }
  3117. }
  3118. }
  3119. // Write the data, but we only overwrite if the command is a macro anyway.
  3120. if(m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI)
  3121. {
  3122. m.command = CMD_SMOOTHMIDI;
  3123. PlugParamValue param = pPlug->GetParameter(static_cast<PlugParamIndex>(paramIndex));
  3124. Limit(param, 0.0f, 1.0f);
  3125. m.param = static_cast<ModCommand::PARAM>(param * 127.0f);
  3126. }
  3127. }
  3128. if(m != mSrc)
  3129. {
  3130. pModDoc->GetPatternUndo().PrepareUndo(pattern, chn, row, 1, 1, "Automation Entry");
  3131. mSrc = m;
  3132. InvalidateCell(PatternCursor(row, chn));
  3133. SetModified(false);
  3134. }
  3135. return 0;
  3136. }
  3137. PatternEditPos CViewPattern::GetEditPos(const CSoundFile &sndFile, const bool liveRecord) const
  3138. {
  3139. PatternEditPos editPos;
  3140. if(liveRecord)
  3141. {
  3142. if(m_nPlayPat != PATTERNINDEX_INVALID)
  3143. {
  3144. editPos.row = m_nPlayRow;
  3145. editPos.order = GetCurrentOrder();
  3146. editPos.pattern = m_nPlayPat;
  3147. } else
  3148. {
  3149. editPos.row = sndFile.m_PlayState.m_nRow;
  3150. editPos.order = sndFile.m_PlayState.m_nCurrentOrder;
  3151. editPos.pattern = sndFile.m_PlayState.m_nPattern;
  3152. }
  3153. if(!sndFile.Patterns.IsValidPat(editPos.pattern) || !sndFile.Patterns[editPos.pattern].IsValidRow(editPos.row))
  3154. {
  3155. editPos.row = GetCurrentRow();
  3156. editPos.order = GetCurrentOrder();
  3157. editPos.pattern = m_nPattern;
  3158. }
  3159. const auto &order = Order();
  3160. if(!order.IsValidPat(editPos.order) || order[editPos.order] != editPos.pattern)
  3161. {
  3162. ORDERINDEX realOrder = order.FindOrder(editPos.pattern, editPos.order);
  3163. if(realOrder != ORDERINDEX_INVALID)
  3164. editPos.order = realOrder;
  3165. }
  3166. } else
  3167. {
  3168. editPos.row = GetCurrentRow();
  3169. editPos.order = GetCurrentOrder();
  3170. editPos.pattern = m_nPattern;
  3171. }
  3172. editPos.channel = GetCurrentChannel();
  3173. return editPos;
  3174. }
  3175. // Return ModCommand at the given cursor position of the current pattern.
  3176. // If the position is not valid, a pointer to a dummy command is returned.
  3177. ModCommand &CViewPattern::GetModCommand(PatternCursor cursor)
  3178. {
  3179. CSoundFile *pSndFile = GetSoundFile();
  3180. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(GetCurrentPattern()) && pSndFile->Patterns[GetCurrentPattern()].IsValidRow(cursor.GetRow()))
  3181. {
  3182. return *pSndFile->Patterns[GetCurrentPattern()].GetpModCommand(cursor.GetRow(), cursor.GetChannel());
  3183. }
  3184. // Failed.
  3185. static ModCommand dummy;
  3186. return dummy;
  3187. }
  3188. // Sanitize cursor so that it can't point to an invalid position in the current pattern.
  3189. void CViewPattern::SanitizeCursor()
  3190. {
  3191. CSoundFile *pSndFile = GetSoundFile();
  3192. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(GetCurrentPattern()))
  3193. {
  3194. m_Cursor.Sanitize(GetSoundFile()->Patterns[m_nPattern].GetNumRows(), GetSoundFile()->Patterns[m_nPattern].GetNumChannels());
  3195. }
  3196. };
  3197. // Returns pointer to modcommand at given position.
  3198. // If the position is not valid, a pointer to a dummy command is returned.
  3199. ModCommand &CViewPattern::GetModCommand(CSoundFile &sndFile, const PatternEditPos &pos)
  3200. {
  3201. static ModCommand dummy;
  3202. if(sndFile.Patterns.IsValidPat(pos.pattern) && pos.row < sndFile.Patterns[pos.pattern].GetNumRows() && pos.channel < sndFile.GetNumChannels())
  3203. return *sndFile.Patterns[pos.pattern].GetpModCommand(pos.row, pos.channel);
  3204. else
  3205. return dummy;
  3206. }
  3207. LRESULT CViewPattern::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM)
  3208. {
  3209. const uint32 midiData = static_cast<uint32>(dwMidiDataParam);
  3210. static uint8 midiVolume = 127;
  3211. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  3212. CModDoc *pModDoc = GetDocument();
  3213. if(pModDoc == nullptr || pMainFrm == nullptr)
  3214. return 0;
  3215. CSoundFile &sndFile = pModDoc->GetSoundFile();
  3216. //Midi message from our perspective:
  3217. // +---------------------------+---------------------------+-------------+-------------+
  3218. //bit: | 24.23.22.21 | 20.19.18.17 | 16.15.14.13 | 12.11.10.09 | 08.07.06.05 | 04.03.02.01 |
  3219. // +---------------------------+---------------------------+-------------+-------------+
  3220. // | Velocity (0-127) | Note (middle C is 60) | Event | Channel |
  3221. // +---------------------------+---------------------------+-------------+-------------+
  3222. //(http://home.roadrunner.com/~jgglatt/tech/midispec.htm)
  3223. //Notes:
  3224. //. Initial midi data handling is done in MidiInCallBack().
  3225. //. If no event is received, previous event is assumed.
  3226. //. A note-on (event=9) with velocity 0 is equivalent to a note off.
  3227. //. Basing the event solely on the velocity as follows is incorrect,
  3228. // since a note-off can have a velocity too:
  3229. // BYTE event = (dwMidiData>>16) & 0x64;
  3230. //. Sample- and instrumentview handle midi mesages in their own methods.
  3231. const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData);
  3232. const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData);
  3233. const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData);
  3234. const uint8 nNote = midiByte1 + NOTE_MIN;
  3235. int vol = midiByte2; // At this stage nVol is a non linear value in [0;127]
  3236. // Need to convert to linear in [0;64] - see below
  3237. MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData);
  3238. if((event == MIDIEvents::evNoteOn) && !vol)
  3239. event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd
  3240. // Handle MIDI mapping.
  3241. PLUGINDEX mappedIndex = uint8_max;
  3242. PlugParamIndex paramIndex = 0;
  3243. uint16 paramValue = uint16_max;
  3244. bool captured = sndFile.GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue);
  3245. // Handle MIDI messages assigned to shortcuts
  3246. CInputHandler *ih = CMainFrame::GetInputHandler();
  3247. if(ih->HandleMIDIMessage(static_cast<InputTargetContext>(kCtxViewPatterns + 1 + m_Cursor.GetColumnType()), midiData) != kcNull
  3248. || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull)
  3249. {
  3250. // Mapped to a command, no need to pass message on.
  3251. captured = true;
  3252. }
  3253. // Write parameter control commands if needed.
  3254. if(paramValue != uint16_max && IsEditingEnabled() && sndFile.GetType() == MOD_TYPE_MPT)
  3255. {
  3256. const bool liveRecord = IsLiveRecord();
  3257. PatternEditPos editPos = GetEditPos(sndFile, liveRecord);
  3258. ModCommand &m = GetModCommand(sndFile, editPos);
  3259. pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, editPos.channel, editPos.row, 1, 1, "MIDI Mapping Record");
  3260. m.Set(NOTE_PCS, mappedIndex, static_cast<uint16>(paramIndex), static_cast<uint16>((paramValue * ModCommand::maxColumnValue) / 16383));
  3261. if(!liveRecord)
  3262. InvalidateRow(editPos.row);
  3263. pModDoc->SetModified();
  3264. pModDoc->UpdateAllViews(this, PatternHint(editPos.pattern).Data(), this);
  3265. }
  3266. if(captured)
  3267. {
  3268. // Event captured by MIDI mapping or shortcut, no need to pass message on.
  3269. return 1;
  3270. }
  3271. const auto &modSpecs = sndFile.GetModSpecifications();
  3272. bool recordParamAsZxx = false;
  3273. switch(event)
  3274. {
  3275. case MIDIEvents::evNoteOff: // Note Off
  3276. if(m_midiSustainActive[channel])
  3277. {
  3278. m_midiSustainBuffer[channel].push_back(midiData);
  3279. return 1;
  3280. }
  3281. // The following method takes care of:
  3282. // . Silencing specific active notes (just setting nNote to 255 as was done before is not acceptible)
  3283. // . Entering a note off in pattern if required
  3284. TempStopNote(nNote, ((TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_RECORDNOTEOFF) != 0));
  3285. break;
  3286. case MIDIEvents::evNoteOn: // Note On
  3287. // Continue playing as soon as MIDI notes are being received
  3288. if((pMainFrm->GetSoundFilePlaying() != &sndFile || sndFile.IsPaused()) && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))
  3289. pModDoc->OnPatternPlayNoLoop();
  3290. vol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume);
  3291. if(vol < 0)
  3292. vol = -1;
  3293. else
  3294. vol = (vol + 3) / 4; //Value from [0,256] to [0,64]
  3295. TempEnterNote(nNote, vol, true);
  3296. break;
  3297. case MIDIEvents::evPolyAftertouch: // Polyphonic aftertouch
  3298. EnterAftertouch(nNote, vol);
  3299. break;
  3300. case MIDIEvents::evChannelAftertouch: // Channel aftertouch
  3301. EnterAftertouch(NOTE_NONE, midiByte1);
  3302. break;
  3303. case MIDIEvents::evPitchBend: // Pitch wheel
  3304. recordParamAsZxx = (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIMACROPITCHBEND) != 0 || modSpecs.HasCommand(CMD_FINETUNE);
  3305. break;
  3306. case MIDIEvents::evControllerChange: //Controller change
  3307. // Checking whether to record MIDI controller change as MIDI macro change.
  3308. // Don't write this if command was already written by MIDI mapping.
  3309. if((paramValue == uint16_max || sndFile.GetType() != MOD_TYPE_MPT)
  3310. && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIMACROCONTROL)
  3311. && !TrackerSettings::Instance().midiIgnoreCCs.Get()[midiByte1 & 0x7F])
  3312. {
  3313. recordParamAsZxx = true;
  3314. }
  3315. switch(midiByte1)
  3316. {
  3317. case MIDIEvents::MIDICC_Volume_Coarse:
  3318. midiVolume = midiByte2;
  3319. break;
  3320. case MIDIEvents::MIDICC_HoldPedal_OnOff:
  3321. m_midiSustainActive[channel] = (midiByte2 >= 0x40);
  3322. if(!m_midiSustainActive[channel])
  3323. {
  3324. // Release all notes
  3325. for(const auto offEvent : m_midiSustainBuffer[channel])
  3326. {
  3327. OnMidiMsg(offEvent, 0);
  3328. }
  3329. m_midiSustainBuffer[channel].clear();
  3330. }
  3331. recordParamAsZxx = false;
  3332. break;
  3333. }
  3334. break;
  3335. case MIDIEvents::evSystem:
  3336. if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_RESPONDTOPLAYCONTROLMSGS)
  3337. {
  3338. // Respond to MIDI song messages
  3339. switch(channel)
  3340. {
  3341. case MIDIEvents::sysStart: //Start song
  3342. pModDoc->OnPlayerPlayFromStart();
  3343. break;
  3344. case MIDIEvents::sysContinue: //Continue song
  3345. pModDoc->OnPlayerPlay();
  3346. break;
  3347. case MIDIEvents::sysStop: //Stop song
  3348. pModDoc->OnPlayerStop();
  3349. break;
  3350. }
  3351. }
  3352. break;
  3353. }
  3354. // Write CC or pitch bend message as MIDI macro change.
  3355. if(recordParamAsZxx && IsEditingEnabled())
  3356. {
  3357. const bool liveRecord = IsLiveRecord();
  3358. const auto editpos = GetEditPos(sndFile, liveRecord);
  3359. ModCommand &m = GetModCommand(sndFile, editpos);
  3360. bool update = false;
  3361. if(event == MIDIEvents::evPitchBend && (m.command == CMD_NONE || m.command == CMD_FINETUNE || m.command == CMD_FINETUNE_SMOOTH) && modSpecs.HasCommand(CMD_FINETUNE))
  3362. {
  3363. pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
  3364. m.command = (m.command == CMD_NONE) ? CMD_FINETUNE : CMD_FINETUNE_SMOOTH;
  3365. m.param = (midiByte2 << 1) | (midiByte1 >> 7);
  3366. update = true;
  3367. } else if(m.IsPcNote())
  3368. {
  3369. pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
  3370. m.SetValueEffectCol(static_cast<decltype(m.GetValueEffectCol())>(Util::muldivr(midiByte2, ModCommand::maxColumnValue, 127)));
  3371. update = true;
  3372. } else if((m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI)
  3373. && (modSpecs.HasCommand(CMD_SMOOTHMIDI) || modSpecs.HasCommand(CMD_MIDI)))
  3374. {
  3375. // Write command only if there's no existing command or already a midi macro command.
  3376. pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
  3377. m.command = modSpecs.HasCommand(CMD_SMOOTHMIDI) ? CMD_SMOOTHMIDI : CMD_MIDI;
  3378. m.param = midiByte2;
  3379. update = true;
  3380. }
  3381. if(update)
  3382. {
  3383. pModDoc->SetModified();
  3384. pModDoc->UpdateAllViews(this, PatternHint(editpos.pattern).Data(), this);
  3385. // Update GUI only if not recording live.
  3386. if(!liveRecord)
  3387. InvalidateRow(editpos.row);
  3388. }
  3389. }
  3390. // Pass MIDI to plugin
  3391. if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDITOPLUG
  3392. && pMainFrm->GetModPlaying() == pModDoc
  3393. && event != MIDIEvents::evNoteOn
  3394. && event != MIDIEvents::evNoteOff)
  3395. {
  3396. const INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
  3397. IMixPlugin *plug = sndFile.GetInstrumentPlugin(instr);
  3398. if(plug)
  3399. {
  3400. plug->MidiSend(midiData);
  3401. // Sending MIDI may modify the plugin. For now, if MIDI data
  3402. // is not active sensing, set modified.
  3403. if(midiData != MIDIEvents::System(MIDIEvents::sysActiveSense))
  3404. pModDoc->SetModified();
  3405. }
  3406. }
  3407. return 1;
  3408. }
  3409. LRESULT CViewPattern::OnModViewMsg(WPARAM wParam, LPARAM lParam)
  3410. {
  3411. switch(wParam)
  3412. {
  3413. case VIEWMSG_SETCTRLWND:
  3414. m_hWndCtrl = (HWND)lParam;
  3415. m_nOrder = static_cast<ORDERINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTORDER));
  3416. SetCurrentPattern(static_cast<PATTERNINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTPATTERN)));
  3417. break;
  3418. case VIEWMSG_GETCURRENTPATTERN:
  3419. return m_nPattern;
  3420. case VIEWMSG_SETCURRENTPATTERN:
  3421. m_nOrder = static_cast<ORDERINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTORDER));
  3422. SetCurrentPattern(static_cast<PATTERNINDEX>(lParam));
  3423. break;
  3424. case VIEWMSG_GETCURRENTPOS:
  3425. return (m_nPattern << 16) | GetCurrentRow();
  3426. case VIEWMSG_FOLLOWSONG:
  3427. m_Status.reset(psFollowSong);
  3428. if(lParam)
  3429. {
  3430. CModDoc *pModDoc = GetDocument();
  3431. m_Status.set(psFollowSong);
  3432. if(pModDoc)
  3433. pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
  3434. if(pModDoc)
  3435. pModDoc->SetFollowWnd(m_hWnd);
  3436. SetFocus();
  3437. } else
  3438. {
  3439. InvalidateRow();
  3440. }
  3441. break;
  3442. case VIEWMSG_PATTERNLOOP:
  3443. SendCtrlMessage(CTRLMSG_PAT_LOOP, lParam);
  3444. break;
  3445. case VIEWMSG_SETRECORD:
  3446. m_Status.set(psRecordingEnabled, !!lParam);
  3447. break;
  3448. case VIEWMSG_SETSPACING:
  3449. m_nSpacing = static_cast<UINT>(lParam);
  3450. break;
  3451. case VIEWMSG_PATTERNPROPERTIES:
  3452. ShowPatternProperties(static_cast<PATTERNINDEX>(lParam));
  3453. GetParentFrame()->SetActiveView(this);
  3454. break;
  3455. case VIEWMSG_SETVUMETERS:
  3456. m_Status.set(psShowVUMeters, !!lParam);
  3457. UpdateSizes();
  3458. UpdateScrollSize();
  3459. InvalidatePattern(true, true);
  3460. break;
  3461. case VIEWMSG_SETPLUGINNAMES:
  3462. m_Status.set(psShowPluginNames, !!lParam);
  3463. UpdateSizes();
  3464. UpdateScrollSize();
  3465. InvalidatePattern(true, true);
  3466. break;
  3467. case VIEWMSG_DOMIDISPACING:
  3468. if(m_nSpacing)
  3469. {
  3470. int temp = timeGetTime();
  3471. if(temp - lParam >= 60)
  3472. {
  3473. CModDoc *pModDoc = GetDocument();
  3474. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  3475. if(!m_Status[psFollowSong]
  3476. || (pMainFrm->GetFollowSong(pModDoc) != m_hWnd)
  3477. || (pModDoc->GetSoundFile().IsPaused()))
  3478. {
  3479. SetCurrentRow(GetCurrentRow() + m_nSpacing);
  3480. }
  3481. } else
  3482. {
  3483. Sleep(0);
  3484. PostMessage(WM_MOD_VIEWMSG, VIEWMSG_DOMIDISPACING, lParam);
  3485. }
  3486. }
  3487. break;
  3488. case VIEWMSG_LOADSTATE:
  3489. if(lParam)
  3490. {
  3491. PATTERNVIEWSTATE *pState = (PATTERNVIEWSTATE *)lParam;
  3492. if(pState->nDetailLevel != PatternCursor::firstColumn)
  3493. m_nDetailLevel = pState->nDetailLevel;
  3494. if(pState->initialized)
  3495. {
  3496. SetCurrentPattern(pState->nPattern);
  3497. // Fix: Horizontal scrollbar pos screwed when selecting with mouse
  3498. SetCursorPosition(pState->cursor);
  3499. SetCurSel(pState->selection);
  3500. }
  3501. }
  3502. break;
  3503. case VIEWMSG_SAVESTATE:
  3504. if(lParam)
  3505. {
  3506. PATTERNVIEWSTATE *pState = (PATTERNVIEWSTATE *)lParam;
  3507. pState->initialized = true;
  3508. pState->nPattern = m_nPattern;
  3509. pState->cursor = m_Cursor;
  3510. pState->selection = m_Selection;
  3511. pState->nDetailLevel = m_nDetailLevel;
  3512. pState->nOrder = GetCurrentOrder();
  3513. }
  3514. break;
  3515. case VIEWMSG_EXPANDPATTERN:
  3516. {
  3517. CModDoc *pModDoc = GetDocument();
  3518. if(pModDoc->ExpandPattern(m_nPattern))
  3519. {
  3520. m_Cursor.SetRow(m_Cursor.GetRow() * 2);
  3521. SetCurrentPattern(m_nPattern);
  3522. }
  3523. break;
  3524. }
  3525. case VIEWMSG_SHRINKPATTERN:
  3526. {
  3527. CModDoc *pModDoc = GetDocument();
  3528. if(pModDoc->ShrinkPattern(m_nPattern))
  3529. {
  3530. m_Cursor.SetRow(m_Cursor.GetRow() / 2);
  3531. SetCurrentPattern(m_nPattern);
  3532. }
  3533. break;
  3534. }
  3535. case VIEWMSG_COPYPATTERN:
  3536. {
  3537. const CSoundFile *pSndFile = GetSoundFile();
  3538. if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
  3539. {
  3540. CopyPattern(m_nPattern, PatternRect(PatternCursor(0, 0), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn)));
  3541. }
  3542. break;
  3543. }
  3544. case VIEWMSG_PASTEPATTERN:
  3545. PastePattern(m_nPattern, PatternCursor(0), PatternClipboard::pmOverwrite);
  3546. InvalidatePattern();
  3547. break;
  3548. case VIEWMSG_AMPLIFYPATTERN:
  3549. OnPatternAmplify();
  3550. break;
  3551. case VIEWMSG_SETDETAIL:
  3552. if(lParam != m_nDetailLevel)
  3553. {
  3554. m_nDetailLevel = static_cast<PatternCursor::Columns>(lParam);
  3555. UpdateSizes();
  3556. UpdateScrollSize();
  3557. SetCurrentColumn(m_Cursor);
  3558. InvalidatePattern(true, true);
  3559. }
  3560. break;
  3561. case VIEWMSG_DOSCROLL:
  3562. OnMouseWheel(0, static_cast<short>(lParam), CPoint(0, 0));
  3563. break;
  3564. default:
  3565. return CModScrollView::OnModViewMsg(wParam, lParam);
  3566. }
  3567. return 0;
  3568. }
  3569. void CViewPattern::CursorJump(int distance, bool snap)
  3570. {
  3571. ROWINDEX row = GetCurrentRow();
  3572. const bool upwards = distance < 0;
  3573. const int distanceAbs = std::abs(distance);
  3574. if(snap && distanceAbs)
  3575. // cppcheck false-positive
  3576. // cppcheck-suppress signConversion
  3577. row = (((row + (upwards ? -1 : 0)) / distanceAbs) + (upwards ? 0 : 1)) * distanceAbs;
  3578. else
  3579. row += distance;
  3580. row = SetCurrentRow(row, true);
  3581. if(IsLiveRecord() && !m_Status[psDragActive])
  3582. {
  3583. CriticalSection cs;
  3584. CSoundFile &sndFile = GetDocument()->GetSoundFile();
  3585. if(m_nOrder != sndFile.m_PlayState.m_nCurrentOrder)
  3586. {
  3587. // We jumped to a different order
  3588. sndFile.ResetChannels();
  3589. sndFile.StopAllVsti();
  3590. }
  3591. sndFile.m_PlayState.m_nCurrentOrder = sndFile.m_PlayState.m_nNextOrder = GetCurrentOrder();
  3592. sndFile.m_PlayState.m_nPattern = m_nPattern;
  3593. sndFile.m_PlayState.m_nRow = m_nPlayRow = row;
  3594. sndFile.m_PlayState.m_nNextRow = m_nNextPlayRow = row + 1;
  3595. // Queue the correct follow-up pattern if we just jumped to the last row.
  3596. if(sndFile.Patterns.IsValidPat(m_nPattern) && m_nNextPlayRow >= sndFile.Patterns[m_nPattern].GetNumRows())
  3597. {
  3598. sndFile.m_PlayState.m_nNextOrder++;
  3599. }
  3600. CMainFrame::GetMainFrame()->ResetNotificationBuffer();
  3601. } else
  3602. {
  3603. if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
  3604. {
  3605. PatternStep(row);
  3606. }
  3607. }
  3608. }
  3609. LRESULT CViewPattern::OnCustomKeyMsg(WPARAM wParam, LPARAM lParam)
  3610. {
  3611. CModDoc *pModDoc = GetDocument();
  3612. if(!pModDoc)
  3613. return kcNull;
  3614. CSoundFile &sndFile = pModDoc->GetSoundFile();
  3615. switch(wParam)
  3616. {
  3617. case kcPrevInstrument: OnPrevInstrument(); return wParam;
  3618. case kcNextInstrument: OnNextInstrument(); return wParam;
  3619. case kcPrevOrder: OnPrevOrder(); return wParam;
  3620. case kcNextOrder: OnNextOrder(); return wParam;
  3621. case kcPatternPlayRow: OnPatternStep(); return wParam;
  3622. case kcPatternRecord: OnPatternRecord(); return wParam;
  3623. case kcCursorCopy: OnCursorCopy(); return wParam;
  3624. case kcCursorPaste: OnCursorPaste(); return wParam;
  3625. case kcChannelMute: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
  3626. OnMuteChannel(c);
  3627. return wParam;
  3628. case kcChannelSolo: OnSoloChannel(GetCurrentChannel()); return wParam;
  3629. case kcChannelUnmuteAll: OnUnmuteAll(); return wParam;
  3630. case kcToggleChanMuteOnPatTransition: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
  3631. TogglePendingMute(c);
  3632. return wParam;
  3633. case kcUnmuteAllChnOnPatTransition: OnPendingUnmuteAllChnFromClick(); return wParam;
  3634. case kcChannelRecordSelect: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
  3635. pModDoc->ToggleChannelRecordGroup(c, RecordGroup::Group1);
  3636. InvalidateChannelsHeaders(); return wParam;
  3637. case kcChannelSplitRecordSelect: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
  3638. pModDoc->ToggleChannelRecordGroup(c, RecordGroup::Group2);
  3639. InvalidateChannelsHeaders(); return wParam;
  3640. case kcChannelReset: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
  3641. ResetChannel(m_Cursor.GetChannel());
  3642. return wParam;
  3643. case kcTimeAtRow: OnShowTimeAtRow(); return wParam;
  3644. case kcSoloChnOnPatTransition: PendingSoloChn(GetCurrentChannel()); return wParam;
  3645. case kcTransposeUp: OnTransposeUp(); return wParam;
  3646. case kcTransposeDown: OnTransposeDown(); return wParam;
  3647. case kcTransposeOctUp: OnTransposeOctUp(); return wParam;
  3648. case kcTransposeOctDown: OnTransposeOctDown(); return wParam;
  3649. case kcTransposeCustom: OnTransposeCustom(); return wParam;
  3650. case kcTransposeCustomQuick: OnTransposeCustomQuick(); return wParam;
  3651. case kcDataEntryUp: DataEntry(true, false); return wParam;
  3652. case kcDataEntryDown: DataEntry(false, false); return wParam;
  3653. case kcDataEntryUpCoarse: DataEntry(true, true); return wParam;
  3654. case kcDataEntryDownCoarse: DataEntry(false, true); return wParam;
  3655. case kcSelectChannel: OnSelectCurrentChannel(); return wParam;
  3656. case kcSelectColumn: OnSelectCurrentColumn(); return wParam;
  3657. case kcPatternAmplify: OnPatternAmplify(); return wParam;
  3658. case kcPatternSetInstrumentNotEmpty:
  3659. case kcPatternSetInstrument: SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(GetCurrentInstrument()), wParam == kcPatternSetInstrument); return wParam;
  3660. case kcPatternInterpolateNote: OnInterpolateNote(); return wParam;
  3661. case kcPatternInterpolateInstr: OnInterpolateInstr(); return wParam;
  3662. case kcPatternInterpolateVol: OnInterpolateVolume(); return wParam;
  3663. case kcPatternInterpolateEffect: OnInterpolateEffect(); return wParam;
  3664. case kcPatternVisualizeEffect: OnVisualizeEffect(); return wParam;
  3665. //case kcPatternOpenRandomizer: OnOpenRandomizer(); return wParam;
  3666. case kcPatternGrowSelection: OnGrowSelection(); return wParam;
  3667. case kcPatternShrinkSelection: OnShrinkSelection(); return wParam;
  3668. // Pattern navigation:
  3669. case kcPatternJumpUph1Select:
  3670. case kcPatternJumpUph1: CursorJump(-(int)GetRowsPerMeasure(), false); return wParam;
  3671. case kcPatternJumpDownh1Select:
  3672. case kcPatternJumpDownh1: CursorJump(GetRowsPerMeasure(), false); return wParam;
  3673. case kcPatternJumpUph2Select:
  3674. case kcPatternJumpUph2: CursorJump(-(int)GetRowsPerBeat(), false); return wParam;
  3675. case kcPatternJumpDownh2Select:
  3676. case kcPatternJumpDownh2: CursorJump(GetRowsPerBeat(), false); return wParam;
  3677. case kcPatternSnapUph1Select:
  3678. case kcPatternSnapUph1: CursorJump(-(int)GetRowsPerMeasure(), true); return wParam;
  3679. case kcPatternSnapDownh1Select:
  3680. case kcPatternSnapDownh1: CursorJump(GetRowsPerMeasure(), true); return wParam;
  3681. case kcPatternSnapUph2Select:
  3682. case kcPatternSnapUph2: CursorJump(-(int)GetRowsPerBeat(), true); return wParam;
  3683. case kcPatternSnapDownh2Select:
  3684. case kcPatternSnapDownh2: CursorJump(GetRowsPerBeat(), true); return wParam;
  3685. case kcNavigateDownSelect:
  3686. case kcNavigateDown: CursorJump(1, false); return wParam;
  3687. case kcNavigateUpSelect:
  3688. case kcNavigateUp: CursorJump(-1, false); return wParam;
  3689. case kcNavigateDownBySpacingSelect:
  3690. case kcNavigateDownBySpacing: CursorJump(m_nSpacing, false); return wParam;
  3691. case kcNavigateUpBySpacingSelect:
  3692. case kcNavigateUpBySpacing: CursorJump(-(int)m_nSpacing, false); return wParam;
  3693. case kcNavigateLeftSelect:
  3694. case kcNavigateLeft:
  3695. MoveCursor(false);
  3696. return wParam;
  3697. case kcNavigateRightSelect:
  3698. case kcNavigateRight:
  3699. MoveCursor(true);
  3700. return wParam;
  3701. case kcNavigateNextChanSelect:
  3702. case kcNavigateNextChan: SetCurrentColumn((GetCurrentChannel() + 1) % sndFile.GetNumChannels(), m_Cursor.GetColumnType()); return wParam;
  3703. case kcNavigatePrevChanSelect:
  3704. case kcNavigatePrevChan:{if(GetCurrentChannel() > 0)
  3705. SetCurrentColumn((GetCurrentChannel() - 1) % sndFile.GetNumChannels(), m_Cursor.GetColumnType());
  3706. else
  3707. SetCurrentColumn(sndFile.GetNumChannels() - 1, m_Cursor.GetColumnType());
  3708. SetSelToCursor();
  3709. return wParam;}
  3710. case kcHomeHorizontalSelect:
  3711. case kcHomeHorizontal: if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
  3712. else if (GetCurrentRow() > 0) SetCurrentRow(0);
  3713. return wParam;
  3714. case kcHomeVerticalSelect:
  3715. case kcHomeVertical: if (GetCurrentRow() > 0) SetCurrentRow(0);
  3716. else if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
  3717. return wParam;
  3718. case kcHomeAbsoluteSelect:
  3719. case kcHomeAbsolute: if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
  3720. if (GetCurrentRow() > 0) SetCurrentRow(0);
  3721. return wParam;
  3722. case kcEndHorizontalSelect:
  3723. case kcEndHorizontal: if (m_Cursor.CompareColumn(PatternCursor(0, sndFile.GetNumChannels() - 1, m_nDetailLevel)) < 0) SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
  3724. else if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
  3725. return wParam;
  3726. case kcEndVerticalSelect:
  3727. case kcEndVertical: if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
  3728. else if (m_Cursor.CompareColumn(PatternCursor(0, sndFile.GetNumChannels() - 1, m_nDetailLevel)) < 0) SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
  3729. return wParam;
  3730. case kcEndAbsoluteSelect:
  3731. case kcEndAbsolute: SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
  3732. if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
  3733. return wParam;
  3734. case kcPrevEntryInColumn:
  3735. case kcNextEntryInColumn:
  3736. JumpToPrevOrNextEntry(wParam == kcNextEntryInColumn, false);
  3737. return wParam;
  3738. case kcPrevEntryInColumnSelect:
  3739. case kcNextEntryInColumnSelect:
  3740. JumpToPrevOrNextEntry(wParam == kcNextEntryInColumnSelect, true);
  3741. return wParam;
  3742. case kcNextPattern: { PATTERNINDEX n = m_nPattern + 1;
  3743. while ((n < sndFile.Patterns.Size()) && !sndFile.Patterns.IsValidPat(n)) n++;
  3744. SetCurrentPattern((n < sndFile.Patterns.Size()) ? n : 0);
  3745. ORDERINDEX currentOrder = GetCurrentOrder();
  3746. ORDERINDEX newOrder = Order().FindOrder(m_nPattern, currentOrder, true);
  3747. if(newOrder != ORDERINDEX_INVALID)
  3748. SetCurrentOrder(newOrder);
  3749. return wParam;
  3750. }
  3751. case kcPrevPattern: { PATTERNINDEX n = (m_nPattern) ? m_nPattern - 1 : sndFile.Patterns.Size() - 1;
  3752. while (n > 0 && !sndFile.Patterns.IsValidPat(n)) n--;
  3753. SetCurrentPattern(n);
  3754. ORDERINDEX currentOrder = GetCurrentOrder();
  3755. ORDERINDEX newOrder = Order().FindOrder(m_nPattern, currentOrder, false);
  3756. if(newOrder != ORDERINDEX_INVALID)
  3757. SetCurrentOrder(newOrder);
  3758. return wParam;
  3759. }
  3760. case kcPrevSequence:
  3761. case kcNextSequence:
  3762. SendCtrlMessage(CTRLMSG_PAT_SETSEQUENCE, mpt::wrapping_modulo(sndFile.Order.GetCurrentSequenceIndex() + (wParam == kcPrevSequence ? -1 : 1), sndFile.Order.GetNumSequences()));
  3763. return wParam;
  3764. case kcSelectWithCopySelect:
  3765. case kcSelectWithNav:
  3766. case kcSelect: if(!m_Status[psDragnDropEdit | psRowSelection | psChannelSelection | psMouseDragSelect]) m_StartSel = m_Cursor;
  3767. m_Status.set(psKeyboardDragSelect);
  3768. return wParam;
  3769. case kcSelectOffWithCopySelect:
  3770. case kcSelectOffWithNav:
  3771. case kcSelectOff: m_Status.reset(psKeyboardDragSelect | psShiftSelect);
  3772. return wParam;
  3773. case kcCopySelectWithSelect:
  3774. case kcCopySelectWithNav:
  3775. case kcCopySelect: if(!m_Status[psDragnDropEdit | psRowSelection | psChannelSelection | psMouseDragSelect]) m_StartSel = m_Cursor;
  3776. m_Status.set(psCtrlDragSelect); return wParam;
  3777. case kcCopySelectOffWithSelect:
  3778. case kcCopySelectOffWithNav:
  3779. case kcCopySelectOff: m_Status.reset(psCtrlDragSelect); return wParam;
  3780. case kcSelectBeat:
  3781. case kcSelectMeasure:
  3782. SelectBeatOrMeasure(wParam == kcSelectBeat); return wParam;
  3783. case kcSelectEvent: SetCurSel(PatternCursor(m_Selection.GetStartRow(), m_Selection.GetStartChannel(), PatternCursor::firstColumn),
  3784. PatternCursor(m_Selection.GetEndRow(), m_Selection.GetEndChannel(), PatternCursor::lastColumn));
  3785. return wParam;
  3786. case kcSelectRow: SetCurSel(PatternCursor(m_Selection.GetStartRow(), 0, PatternCursor::firstColumn),
  3787. PatternCursor(m_Selection.GetEndRow(), sndFile.GetNumChannels(), PatternCursor::lastColumn));
  3788. return wParam;
  3789. case kcClearRow: OnClearField(RowMask(), false); return wParam;
  3790. case kcClearField: OnClearField(RowMask(m_Cursor), false); return wParam;
  3791. case kcClearFieldITStyle: OnClearField(RowMask(m_Cursor), false, true); return wParam;
  3792. case kcClearRowStep: OnClearField(RowMask(), true); return wParam;
  3793. case kcClearFieldStep: OnClearField(RowMask(m_Cursor), true); return wParam;
  3794. case kcClearFieldStepITStyle: OnClearField(RowMask(m_Cursor), true, true); return wParam;
  3795. case kcDeleteRow: OnDeleteRow(); return wParam;
  3796. case kcDeleteWholeRow: OnDeleteWholeRow(); return wParam;
  3797. case kcDeleteRowGlobal: OnDeleteRowGlobal(); return wParam;
  3798. case kcDeleteWholeRowGlobal: OnDeleteWholeRowGlobal(); return wParam;
  3799. case kcInsertRow: OnInsertRow(); return wParam;
  3800. case kcInsertWholeRow: OnInsertWholeRow(); return wParam;
  3801. case kcInsertRowGlobal: OnInsertRowGlobal(); return wParam;
  3802. case kcInsertWholeRowGlobal: OnInsertWholeRowGlobal(); return wParam;
  3803. case kcShowNoteProperties: ShowEditWindow(); return wParam;
  3804. case kcShowPatternProperties: OnPatternProperties(); return wParam;
  3805. case kcShowSplitKeyboardSettings: SetSplitKeyboardSettings(); return wParam;
  3806. case kcShowEditMenu:
  3807. {
  3808. CPoint pt = GetPointFromPosition(m_Cursor);
  3809. pt.x += GetChannelWidth() / 2;
  3810. pt.y += GetRowHeight() / 2;
  3811. OnRButtonDown(0, pt);
  3812. }
  3813. return wParam;
  3814. case kcShowChannelCtxMenu:
  3815. {
  3816. CPoint pt = GetPointFromPosition(m_Cursor);
  3817. pt.x += GetChannelWidth() / 2;
  3818. pt.y = (m_szHeader.cy - m_szPluginHeader.cy) / 2;
  3819. OnRButtonDown(0, pt);
  3820. }
  3821. return wParam;
  3822. case kcShowChannelPluginCtxMenu:
  3823. {
  3824. CPoint pt = GetPointFromPosition(m_Cursor);
  3825. pt.x += GetChannelWidth() / 2;
  3826. pt.y = m_szHeader.cy - m_szPluginHeader.cy / 2;
  3827. OnRButtonDown(0, pt);
  3828. }
  3829. return wParam;
  3830. case kcPatternGoto: OnEditGoto(); return wParam;
  3831. case kcNoteCut: TempEnterNote(NOTE_NOTECUT); return wParam;
  3832. case kcNoteOff: TempEnterNote(NOTE_KEYOFF); return wParam;
  3833. case kcNoteFade: TempEnterNote(NOTE_FADE); return wParam;
  3834. case kcNotePC: TempEnterNote(NOTE_PC); return wParam;
  3835. case kcNotePCS: TempEnterNote(NOTE_PCS); return wParam;
  3836. case kcEditUndo: OnEditUndo(); return wParam;
  3837. case kcEditRedo: OnEditRedo(); return wParam;
  3838. case kcEditFind: OnEditFind(); return wParam;
  3839. case kcEditFindNext: OnEditFindNext(); return wParam;
  3840. case kcEditCut: OnEditCut(); return wParam;
  3841. case kcEditCopy: OnEditCopy(); return wParam;
  3842. case kcCopyAndLoseSelection:
  3843. OnEditCopy();
  3844. [[fallthrough]];
  3845. case kcLoseSelection:
  3846. SetSelToCursor();
  3847. return wParam;
  3848. case kcEditPaste: OnEditPaste(); return wParam;
  3849. case kcEditMixPaste: OnEditMixPaste(); return wParam;
  3850. case kcEditMixPasteITStyle: OnEditMixPasteITStyle(); return wParam;
  3851. case kcEditPasteFlood: OnEditPasteFlood(); return wParam;
  3852. case kcEditPushForwardPaste: OnEditPushForwardPaste(); return wParam;
  3853. case kcEditSelectAll: OnEditSelectAll(); return wParam;
  3854. case kcTogglePluginEditor: TogglePluginEditor(GetCurrentChannel()); return wParam;
  3855. case kcToggleFollowSong: SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 1); return wParam;
  3856. case kcChangeLoopStatus: SendCtrlMessage(CTRLMSG_PAT_LOOP, -1); return wParam;
  3857. case kcNewPattern: SendCtrlMessage(CTRLMSG_PAT_NEWPATTERN); return wParam;
  3858. case kcDuplicatePattern: SendCtrlMessage(CTRLMSG_PAT_DUPPATTERN); return wParam;
  3859. case kcSwitchToOrderList: OnSwitchToOrderList(); return wParam;
  3860. case kcToggleOverflowPaste: TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_OVERFLOWPASTE; return wParam;
  3861. case kcToggleNoteOffRecordPC: TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_KBDNOTEOFF; return wParam;
  3862. case kcToggleNoteOffRecordMIDI: TrackerSettings::Instance().m_dwMidiSetup ^= MIDISETUP_RECORDNOTEOFF; return wParam;
  3863. case kcPatternEditPCNotePlugin: OnTogglePCNotePluginEditor(); return wParam;
  3864. case kcQuantizeSettings: OnSetQuantize(); return wParam;
  3865. case kcLockPlaybackToRows: OnLockPatternRows(); return wParam;
  3866. case kcFindInstrument: FindInstrument(); return wParam;
  3867. case kcChannelSettings:
  3868. {
  3869. // Open centered Quick Channel Settings dialog.
  3870. CRect windowPos;
  3871. GetWindowRect(windowPos);
  3872. m_quickChannelProperties.Show(GetDocument(), m_Cursor.GetChannel(), CPoint(windowPos.left + windowPos.Width() / 2, windowPos.top + windowPos.Height() / 2));
  3873. return wParam;
  3874. }
  3875. case kcChannelTranspose: m_MenuCursor = m_Cursor; OnTransposeChannel(); return wParam;
  3876. case kcChannelDuplicate: m_MenuCursor = m_Cursor; OnDuplicateChannel(); return wParam;
  3877. case kcChannelAddBefore: m_MenuCursor = m_Cursor; OnAddChannelFront(); return wParam;
  3878. case kcChannelAddAfter: m_MenuCursor = m_Cursor; OnAddChannelAfter(); return wParam;
  3879. case kcChannelRemove: m_MenuCursor = m_Cursor; OnRemoveChannel(); return wParam;
  3880. case kcChannelMoveLeft:
  3881. if(CHANNELINDEX chn = m_Selection.GetStartChannel(); chn > 0)
  3882. DragChannel(chn, chn - 1u, m_Selection.GetNumChannels(), false);
  3883. return wParam;
  3884. case kcChannelMoveRight:
  3885. if (CHANNELINDEX chn = m_Selection.GetStartChannel(); chn < sndFile.GetNumChannels() - m_Selection.GetNumChannels())
  3886. DragChannel(chn, chn + 1u, m_Selection.GetNumChannels(), false);
  3887. return wParam;
  3888. case kcSplitPattern: m_MenuCursor = m_Cursor; OnSplitPattern(); return wParam;
  3889. case kcDecreaseSpacing:
  3890. if(m_nSpacing > 0) SetSpacing(m_nSpacing - 1);
  3891. return wParam;
  3892. case kcIncreaseSpacing:
  3893. if(m_nSpacing < MAX_SPACING) SetSpacing(m_nSpacing + 1);
  3894. return wParam;
  3895. case kcChordEditor:
  3896. {
  3897. CChordEditor dlg(this);
  3898. dlg.DoModal();
  3899. return wParam;
  3900. }
  3901. // Clipboard Manager
  3902. case kcToggleClipboardManager:
  3903. PatternClipboardDialog::Toggle();
  3904. return wParam;
  3905. case kcClipboardPrev:
  3906. PatternClipboard::CycleBackward();
  3907. PatternClipboardDialog::UpdateList();
  3908. return wParam;
  3909. case kcClipboardNext:
  3910. PatternClipboard::CycleForward();
  3911. PatternClipboardDialog::UpdateList();
  3912. return wParam;
  3913. case kcCutPatternChannel:
  3914. PatternClipboard::Copy(sndFile, GetCurrentPattern(), GetCurrentChannel());
  3915. OnEditSelectChannel();
  3916. OnClearSelection(false);
  3917. return wParam;
  3918. case kcCutPattern:
  3919. PatternClipboard::Copy(sndFile, GetCurrentPattern());
  3920. OnEditSelectAll();
  3921. OnClearSelection(false);
  3922. return wParam;
  3923. case kcCopyPatternChannel:
  3924. PatternClipboard::Copy(sndFile, GetCurrentPattern(), GetCurrentChannel());
  3925. return wParam;
  3926. case kcCopyPattern:
  3927. PatternClipboard::Copy(sndFile, GetCurrentPattern());
  3928. return wParam;
  3929. case kcPastePatternChannel:
  3930. case kcPastePattern:
  3931. if(PatternClipboard::Paste(sndFile, GetCurrentPattern(), wParam == kcPastePatternChannel ? GetCurrentChannel() : CHANNELINDEX_INVALID))
  3932. {
  3933. SetModified();
  3934. InvalidatePattern();
  3935. GetDocument()->UpdateAllViews(this, PatternHint(GetCurrentPattern()).Data(), this);
  3936. }
  3937. return wParam;
  3938. case kcTogglePatternPlayRow:
  3939. TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_PLAYNAVIGATEROW;
  3940. CMainFrame::GetMainFrame()->SetHelpText((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
  3941. ? _T("Play whole row when navigatin was turned is now enabled.") : _T("Play whole row when navigatin was turned is now disabled."));
  3942. return wParam;
  3943. }
  3944. // Ignore note entry if it is on key hold and user is in key-jazz mode or edit step is 0 (so repeated entry would be useless)
  3945. const auto keyCombination = KeyCombination::FromLPARAM(lParam);
  3946. const bool enterNote = keyCombination.EventType() != kKeyEventRepeat || (IsEditingEnabled() && m_nSpacing != 0);
  3947. // Ranges:
  3948. if(wParam >= kcVPStartNotes && wParam <= kcVPEndNotes)
  3949. {
  3950. if(enterNote)
  3951. TempEnterNote(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartNotes)));
  3952. return wParam;
  3953. } else if(wParam >= kcVPStartChords && wParam <= kcVPEndChords)
  3954. {
  3955. if(enterNote)
  3956. TempEnterChord(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartChords)));
  3957. return wParam;
  3958. }
  3959. if(wParam >= kcVPStartNoteStops && wParam <= kcVPEndNoteStops)
  3960. {
  3961. TempStopNote(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartNoteStops)));
  3962. return wParam;
  3963. } else if(wParam >= kcVPStartChordStops && wParam <= kcVPEndChordStops)
  3964. {
  3965. TempStopChord(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartChordStops)));
  3966. return wParam;
  3967. }
  3968. if(wParam >= kcSetSpacing0 && wParam <= kcSetSpacing9)
  3969. {
  3970. SetSpacing(static_cast<int>(wParam) - kcSetSpacing0);
  3971. return wParam;
  3972. }
  3973. if(wParam >= kcSetIns0 && wParam <= kcSetIns9)
  3974. {
  3975. if(IsEditingEnabled_bmsg())
  3976. TempEnterIns(static_cast<int>(wParam) - kcSetIns0);
  3977. return wParam;
  3978. }
  3979. if(wParam >= kcSetOctave0 && wParam <= kcSetOctave9)
  3980. {
  3981. if(IsEditingEnabled_bmsg())
  3982. TempEnterOctave(static_cast<int>(wParam) - kcSetOctave0);
  3983. return wParam;
  3984. }
  3985. if(wParam >= kcSetOctaveStop0 && wParam <= kcSetOctaveStop9)
  3986. {
  3987. TempStopOctave(static_cast<int>(wParam) - kcSetOctaveStop0);
  3988. return wParam;
  3989. }
  3990. if(wParam >= kcSetVolumeStart && wParam <= kcSetVolumeEnd)
  3991. {
  3992. if(IsEditingEnabled_bmsg())
  3993. TempEnterVol(static_cast<int>(wParam) - kcSetVolumeStart);
  3994. return wParam;
  3995. }
  3996. if(wParam >= kcSetFXStart && wParam <= kcSetFXEnd)
  3997. {
  3998. if(IsEditingEnabled_bmsg())
  3999. TempEnterFX(static_cast<ModCommand::COMMAND>(wParam - kcSetFXStart + 1));
  4000. return wParam;
  4001. }
  4002. if(wParam >= kcSetFXParam0 && wParam <= kcSetFXParamF)
  4003. {
  4004. if(IsEditingEnabled_bmsg())
  4005. TempEnterFXparam(static_cast<int>(wParam) - kcSetFXParam0);
  4006. return wParam;
  4007. }
  4008. return kcNull;
  4009. }
  4010. // Move pattern cursor to left or right, respecting invisible columns.
  4011. void CViewPattern::MoveCursor(bool moveRight)
  4012. {
  4013. if(!moveRight)
  4014. {
  4015. // Move cursor one column to the left
  4016. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP) && m_Cursor.IsInFirstColumn())
  4017. {
  4018. // Wrap around to last channel
  4019. SetCurrentColumn(GetDocument()->GetNumChannels() - 1, m_nDetailLevel);
  4020. } else if(!m_Cursor.IsInFirstColumn())
  4021. {
  4022. m_Cursor.Move(0, 0, -1);
  4023. SetCurrentColumn(m_Cursor);
  4024. }
  4025. } else
  4026. {
  4027. // Move cursor one column to the right
  4028. const PatternCursor rightmost(0, GetDocument()->GetNumChannels() - 1, m_nDetailLevel);
  4029. if(m_Cursor.CompareColumn(rightmost) >= 0)
  4030. {
  4031. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP))
  4032. {
  4033. // Wrap around to first channel.
  4034. SetCurrentColumn(0);
  4035. } else
  4036. {
  4037. SetCurrentColumn(rightmost);
  4038. }
  4039. } else
  4040. {
  4041. do
  4042. {
  4043. m_Cursor.Move(0, 0, 1);
  4044. } while(m_Cursor.GetColumnType() > m_nDetailLevel);
  4045. SetCurrentColumn(m_Cursor);
  4046. }
  4047. }
  4048. }
  4049. static bool EnterPCNoteValue(int v, ModCommand &m, uint16 (ModCommand::*getMethod)() const, void (ModCommand::*setMethod)(uint16))
  4050. {
  4051. if(v < 0 || v > 9)
  4052. return false;
  4053. uint16 val = (m.*getMethod)();
  4054. // Move existing digits to left, drop out leftmost digit and push new digit to the least significant digit.
  4055. val = static_cast<uint16>((val % 100) * 10 + v);
  4056. LimitMax(val, static_cast<uint16>(ModCommand::maxColumnValue));
  4057. (m.*setMethod)(val);
  4058. return true;
  4059. }
  4060. // Enter volume effect / number in the pattern.
  4061. void CViewPattern::TempEnterVol(int v)
  4062. {
  4063. CSoundFile *pSndFile = GetSoundFile();
  4064. if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
  4065. return;
  4066. PrepareUndo(m_Cursor, m_Cursor, "Volume Entry");
  4067. ModCommand &target = GetCursorCommand();
  4068. ModCommand oldcmd = target; // This is the command we are about to overwrite
  4069. const bool isDigit = (v >= 0) && (v <= 9);
  4070. if(target.IsPcNote())
  4071. {
  4072. if(EnterPCNoteValue(v, target, &ModCommand::GetValueVolCol, &ModCommand::SetValueVolCol))
  4073. m_PCNoteEditMemory = target;
  4074. } else
  4075. {
  4076. ModCommand::VOLCMD volcmd = target.volcmd;
  4077. uint16 vol = target.vol;
  4078. if(isDigit)
  4079. {
  4080. vol = ((vol * 10) + v) % 100;
  4081. if(!volcmd)
  4082. volcmd = VOLCMD_VOLUME;
  4083. } else
  4084. {
  4085. switch(v + kcSetVolumeStart)
  4086. {
  4087. case kcSetVolumeVol: volcmd = VOLCMD_VOLUME; break;
  4088. case kcSetVolumePan: volcmd = VOLCMD_PANNING; break;
  4089. case kcSetVolumeVolSlideUp: volcmd = VOLCMD_VOLSLIDEUP; break;
  4090. case kcSetVolumeVolSlideDown: volcmd = VOLCMD_VOLSLIDEDOWN; break;
  4091. case kcSetVolumeFineVolUp: volcmd = VOLCMD_FINEVOLUP; break;
  4092. case kcSetVolumeFineVolDown: volcmd = VOLCMD_FINEVOLDOWN; break;
  4093. case kcSetVolumeVibratoSpd: volcmd = VOLCMD_VIBRATOSPEED; break;
  4094. case kcSetVolumeVibrato: volcmd = VOLCMD_VIBRATODEPTH; break;
  4095. case kcSetVolumeXMPanLeft: volcmd = VOLCMD_PANSLIDELEFT; break;
  4096. case kcSetVolumeXMPanRight: volcmd = VOLCMD_PANSLIDERIGHT; break;
  4097. case kcSetVolumePortamento: volcmd = VOLCMD_TONEPORTAMENTO; break;
  4098. case kcSetVolumeITPortaUp: volcmd = VOLCMD_PORTAUP; break;
  4099. case kcSetVolumeITPortaDown: volcmd = VOLCMD_PORTADOWN; break;
  4100. case kcSetVolumeITOffset: volcmd = VOLCMD_OFFSET; break;
  4101. }
  4102. if(target.volcmd == VOLCMD_NONE && volcmd == m_cmdOld.volcmd)
  4103. {
  4104. vol = m_cmdOld.vol;
  4105. }
  4106. }
  4107. uint16 max;
  4108. switch(volcmd)
  4109. {
  4110. case VOLCMD_VOLUME:
  4111. case VOLCMD_PANNING:
  4112. max = 64;
  4113. break;
  4114. default:
  4115. max = (pSndFile->GetType() == MOD_TYPE_XM) ? 0x0F : 9;
  4116. break;
  4117. }
  4118. if(vol > max)
  4119. vol %= 10;
  4120. if(pSndFile->GetModSpecifications().HasVolCommand(volcmd))
  4121. {
  4122. m_cmdOld.volcmd = target.volcmd = volcmd;
  4123. m_cmdOld.vol = target.vol = static_cast<ModCommand::VOL>(vol);
  4124. }
  4125. }
  4126. SetSelToCursor();
  4127. if(oldcmd != target)
  4128. {
  4129. SetModified(false);
  4130. InvalidateCell(m_Cursor);
  4131. UpdateIndicator();
  4132. }
  4133. // Cursor step for command letter
  4134. if(!target.IsPcNote() && !isDigit && m_nSpacing > 0 && !IsLiveRecord() && TrackerSettings::Instance().patternStepCommands)
  4135. {
  4136. if(m_Cursor.GetRow() + m_nSpacing < pSndFile->Patterns[m_nPattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
  4137. {
  4138. SetCurrentRow(m_Cursor.GetRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
  4139. }
  4140. }
  4141. }
  4142. void CViewPattern::SetSpacing(int n)
  4143. {
  4144. if(static_cast<UINT>(n) != m_nSpacing)
  4145. {
  4146. m_nSpacing = static_cast<UINT>(n);
  4147. PostCtrlMessage(CTRLMSG_SETSPACING, m_nSpacing);
  4148. }
  4149. }
  4150. // Enter an effect letter in the pattern
  4151. void CViewPattern::TempEnterFX(ModCommand::COMMAND c, int v)
  4152. {
  4153. CSoundFile *pSndFile = GetSoundFile();
  4154. if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
  4155. {
  4156. return;
  4157. }
  4158. ModCommand &target = GetCursorCommand();
  4159. ModCommand oldcmd = target; // This is the command we are about to overwrite
  4160. PrepareUndo(m_Cursor, m_Cursor, "Effect Entry");
  4161. if(target.IsPcNote())
  4162. {
  4163. if(EnterPCNoteValue(c, target, &ModCommand::GetValueEffectCol, &ModCommand::SetValueEffectCol))
  4164. m_PCNoteEditMemory = target;
  4165. } else if(pSndFile->GetModSpecifications().HasCommand(c))
  4166. {
  4167. if(c != CMD_NONE)
  4168. {
  4169. if((c == m_cmdOld.command) && (!target.param) && (target.command == CMD_NONE))
  4170. {
  4171. target.param = m_cmdOld.param;
  4172. } else
  4173. {
  4174. m_cmdOld.param = 0;
  4175. }
  4176. m_cmdOld.command = c;
  4177. }
  4178. target.command = c;
  4179. if(v >= 0)
  4180. {
  4181. target.param = static_cast<ModCommand::PARAM>(v);
  4182. }
  4183. // Check for MOD/XM Speed/Tempo command
  4184. if((pSndFile->GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))
  4185. && (target.command == CMD_SPEED || target.command == CMD_TEMPO))
  4186. {
  4187. target.command = static_cast<ModCommand::COMMAND>((target.param <= pSndFile->GetModSpecifications().speedMax) ? CMD_SPEED : CMD_TEMPO);
  4188. }
  4189. }
  4190. SetSelToCursor();
  4191. if(oldcmd != target)
  4192. {
  4193. SetModified(false);
  4194. InvalidateCell(m_Cursor);
  4195. UpdateIndicator();
  4196. }
  4197. // Cursor step for command letter
  4198. if(!target.IsPcNote() && m_nSpacing > 0 && !IsLiveRecord() && TrackerSettings::Instance().patternStepCommands)
  4199. {
  4200. if(m_Cursor.GetRow() + m_nSpacing < pSndFile->Patterns[m_nPattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
  4201. {
  4202. SetCurrentRow(m_Cursor.GetRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
  4203. }
  4204. }
  4205. }
  4206. // Enter an effect param in the pattenr
  4207. void CViewPattern::TempEnterFXparam(int v)
  4208. {
  4209. CSoundFile *pSndFile = GetSoundFile();
  4210. if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
  4211. {
  4212. return;
  4213. }
  4214. ModCommand &target = GetCursorCommand();
  4215. ModCommand oldcmd = target; // This is the command we are about to overwrite
  4216. PrepareUndo(m_Cursor, m_Cursor, "Parameter Entry");
  4217. if(target.IsPcNote())
  4218. {
  4219. if(EnterPCNoteValue(v, target, &ModCommand::GetValueEffectCol, &ModCommand::SetValueEffectCol))
  4220. m_PCNoteEditMemory = target;
  4221. } else
  4222. {
  4223. target.param = static_cast<ModCommand::PARAM>((target.param << 4) | v);
  4224. if(target.command == m_cmdOld.command)
  4225. {
  4226. m_cmdOld.param = target.param;
  4227. }
  4228. // Check for MOD/XM Speed/Tempo command
  4229. if((pSndFile->GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))
  4230. && (target.command == CMD_SPEED || target.command == CMD_TEMPO))
  4231. {
  4232. target.command = static_cast<ModCommand::COMMAND>((target.param <= pSndFile->GetModSpecifications().speedMax) ? CMD_SPEED : CMD_TEMPO);
  4233. }
  4234. }
  4235. SetSelToCursor();
  4236. if(target != oldcmd)
  4237. {
  4238. SetModified(false);
  4239. InvalidateCell(m_Cursor);
  4240. UpdateIndicator();
  4241. }
  4242. }
  4243. // Stop a note that has been entered
  4244. void CViewPattern::TempStopNote(ModCommand::NOTE note, const bool fromMidi, bool chordMode)
  4245. {
  4246. CModDoc *pModDoc = GetDocument();
  4247. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  4248. if(pModDoc == nullptr || pMainFrm == nullptr || !ModCommand::IsNote(note))
  4249. {
  4250. return;
  4251. }
  4252. CSoundFile &sndFile = pModDoc->GetSoundFile();
  4253. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  4254. {
  4255. return;
  4256. }
  4257. const CModSpecifications &specs = sndFile.GetModSpecifications();
  4258. Limit(note, specs.noteMin, specs.noteMax);
  4259. const bool liveRecord = IsLiveRecord();
  4260. const bool isSplit = IsNoteSplit(note);
  4261. UINT ins = 0;
  4262. chordMode = chordMode && (m_prevChordNote != NOTE_NONE);
  4263. auto &activeNoteMap = isSplit ? m_splitActiveNoteChannel : m_activeNoteChannel;
  4264. const CHANNELINDEX nChnCursor = GetCurrentChannel();
  4265. const CHANNELINDEX nChn = chordMode ? m_chordPatternChannels[0] : (activeNoteMap[note] < sndFile.GetNumChannels() ? activeNoteMap[note] : nChnCursor);
  4266. CHANNELINDEX noteChannels[MPTChord::notesPerChord] = {nChn};
  4267. ModCommand::NOTE notes[MPTChord::notesPerChord] = {note};
  4268. int numNotes = 1;
  4269. if(pModDoc)
  4270. {
  4271. if(isSplit)
  4272. {
  4273. ins = pModDoc->GetSplitKeyboardSettings().splitInstrument;
  4274. if(pModDoc->GetSplitKeyboardSettings().octaveLink)
  4275. {
  4276. int trNote = note + 12 * pModDoc->GetSplitKeyboardSettings().octaveModifier;
  4277. Limit(trNote, specs.noteMin, specs.noteMax);
  4278. note = static_cast<ModCommand::NOTE>(trNote);
  4279. }
  4280. }
  4281. if(!ins)
  4282. ins = GetCurrentInstrument();
  4283. if(!ins)
  4284. ins = m_fallbackInstrument;
  4285. if(chordMode)
  4286. {
  4287. m_Status.reset(psChordPlaying);
  4288. numNotes = ConstructChord(note, notes, m_prevChordBaseNote);
  4289. if(!numNotes)
  4290. {
  4291. return;
  4292. }
  4293. for(int i = 0; i < numNotes; i++)
  4294. {
  4295. pModDoc->NoteOff(notes[i], true, static_cast<INSTRUMENTINDEX>(ins), m_noteChannel[notes[i] - NOTE_MIN]);
  4296. m_noteChannel[notes[i] - NOTE_MIN] = CHANNELINDEX_INVALID;
  4297. m_baPlayingNote.reset(notes[i]);
  4298. noteChannels[i] = m_chordPatternChannels[i];
  4299. }
  4300. m_prevChordNote = NOTE_NONE;
  4301. } else
  4302. {
  4303. m_baPlayingNote.reset(note);
  4304. pModDoc->NoteOff(note, ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOTEFADE) || sndFile.GetNumInstruments() == 0), static_cast<INSTRUMENTINDEX>(ins), m_noteChannel[note - NOTE_MIN]);
  4305. m_noteChannel[note - NOTE_MIN] = CHANNELINDEX_INVALID;
  4306. }
  4307. }
  4308. // Enter note off in pattern?
  4309. if(!ModCommand::IsNote(note))
  4310. return;
  4311. if(m_Cursor.GetColumnType() > PatternCursor::instrColumn && (chordMode || !fromMidi))
  4312. return;
  4313. if(!pModDoc || !pMainFrm || !(IsEditingEnabled()))
  4314. return;
  4315. activeNoteMap[note] = NOTE_CHANNEL_MAP_INVALID; //unlock channel
  4316. if(!((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_KBDNOTEOFF) || fromMidi))
  4317. {
  4318. // We don't want to write the note-off into the pattern if this feature is disabled and we're not recording from MIDI.
  4319. return;
  4320. }
  4321. // -- write sdx if playing live
  4322. const bool usePlaybackPosition = (!chordMode) && (liveRecord && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_AUTODELAY));
  4323. //Work out where to put the note off
  4324. PatternEditPos editPos = GetEditPos(sndFile, usePlaybackPosition);
  4325. const bool doQuantize = (liveRecord || (fromMidi && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))) && TrackerSettings::Instance().recordQuantizeRows != 0;
  4326. if(doQuantize)
  4327. {
  4328. QuantizeRow(editPos.pattern, editPos.row);
  4329. }
  4330. ModCommand *pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
  4331. // Don't overwrite:
  4332. if(pTarget->note != NOTE_NONE || pTarget->instr || pTarget->volcmd != VOLCMD_NONE)
  4333. {
  4334. // If there's a note in the current location and the song is playing and following,
  4335. // the user probably just tapped the key - let's try the next row down.
  4336. editPos.row++;
  4337. if(pTarget->note == note && liveRecord && sndFile.Patterns[editPos.pattern].IsValidRow(editPos.row))
  4338. {
  4339. pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
  4340. if(pTarget->note != NOTE_NONE || (!chordMode && (pTarget->instr || pTarget->volcmd)))
  4341. return;
  4342. } else
  4343. {
  4344. return;
  4345. }
  4346. }
  4347. bool modified = false;
  4348. for(int i = 0; i < numNotes; i++)
  4349. {
  4350. if(m_previousNote[noteChannels[i]] != notes[i])
  4351. {
  4352. // This might be a note-off from a past note, but since we already hit a new note on this channel, we ignore it.
  4353. continue;
  4354. }
  4355. if(!modified)
  4356. {
  4357. pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, nChn, editPos.row, noteChannels[numNotes - 1] - nChn + 1, 1, "Note Stop Entry");
  4358. modified = true;
  4359. }
  4360. pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, noteChannels[i]);
  4361. // -- write sdx if playing live
  4362. if(usePlaybackPosition && m_nPlayTick && pTarget->command == CMD_NONE && !doQuantize)
  4363. {
  4364. pTarget->command = CMD_S3MCMDEX;
  4365. if(!specs.HasCommand(CMD_S3MCMDEX))
  4366. pTarget->command = CMD_MODCMDEX;
  4367. pTarget->param = static_cast<ModCommand::PARAM>(0xD0 | std::min(uint8(0xF), mpt::saturate_cast<uint8>(m_nPlayTick)));
  4368. }
  4369. //Enter note off
  4370. if(sndFile.GetModSpecifications().hasNoteOff && (sndFile.GetNumInstruments() > 0 || !sndFile.GetModSpecifications().hasNoteCut))
  4371. {
  4372. // ===
  4373. // Not used in sample (if module format supports ^^^ instead)
  4374. pTarget->note = NOTE_KEYOFF;
  4375. } else if(sndFile.GetModSpecifications().hasNoteCut)
  4376. {
  4377. // ^^^
  4378. pTarget->note = NOTE_NOTECUT;
  4379. } else
  4380. {
  4381. // we don't have anything to cut (MOD format) - use volume or ECx
  4382. if(usePlaybackPosition && m_nPlayTick && !doQuantize) // ECx
  4383. {
  4384. pTarget->command = CMD_S3MCMDEX;
  4385. if(!specs.HasCommand(CMD_S3MCMDEX))
  4386. pTarget->command = CMD_MODCMDEX;
  4387. pTarget->param = static_cast<ModCommand::PARAM>(0xC0 | std::min(uint8(0xF), mpt::saturate_cast<uint8>(m_nPlayTick)));
  4388. } else // C00
  4389. {
  4390. pTarget->note = NOTE_NONE;
  4391. pTarget->command = CMD_VOLUME;
  4392. pTarget->param = 0;
  4393. }
  4394. }
  4395. pTarget->instr = 0; // Instrument numbers next to note-offs can do all kinds of weird things in XM files, and they are pointless anyway.
  4396. pTarget->volcmd = VOLCMD_NONE;
  4397. pTarget->vol = 0;
  4398. }
  4399. if(!modified)
  4400. return;
  4401. SetModified(false);
  4402. if(editPos.pattern == m_nPattern)
  4403. {
  4404. InvalidateRow(editPos.row);
  4405. } else
  4406. {
  4407. InvalidatePattern();
  4408. }
  4409. // Update only if not recording live.
  4410. if(!liveRecord)
  4411. {
  4412. UpdateIndicator();
  4413. }
  4414. return;
  4415. }
  4416. // Enter an octave number in the pattern
  4417. void CViewPattern::TempEnterOctave(int val)
  4418. {
  4419. const CSoundFile *pSndFile = GetSoundFile();
  4420. if(pSndFile == nullptr)
  4421. {
  4422. return;
  4423. }
  4424. const ModCommand &target = GetCursorCommand();
  4425. if(target.IsNote())
  4426. {
  4427. int groupSize = GetDocument()->GetInstrumentGroupSize(target.instr);
  4428. // The following might look a bit convoluted... This is mostly because the "middle-C" in
  4429. // custom tunings always has octave 5, no matter how many octaves the tuning actually has.
  4430. int note = mpt::wrapping_modulo(target.note - NOTE_MIDDLEC, groupSize) + (val - 5) * groupSize + NOTE_MIDDLEC;
  4431. Limit(note, NOTE_MIN, NOTE_MAX);
  4432. TempEnterNote(static_cast<ModCommand::NOTE>(note));
  4433. // Memorize note for key-up
  4434. ASSERT(size_t(val) < m_octaveKeyMemory.size());
  4435. m_octaveKeyMemory[val] = target.note;
  4436. }
  4437. }
  4438. // Stop note that has been triggered by entering an octave in the pattern.
  4439. void CViewPattern::TempStopOctave(int val)
  4440. {
  4441. ASSERT(size_t(val) < m_octaveKeyMemory.size());
  4442. if(m_octaveKeyMemory[val] != NOTE_NONE)
  4443. {
  4444. TempStopNote(m_octaveKeyMemory[val]);
  4445. m_octaveKeyMemory[val] = NOTE_NONE;
  4446. }
  4447. }
  4448. // Enter an instrument number in the pattern
  4449. void CViewPattern::TempEnterIns(int val)
  4450. {
  4451. CSoundFile *pSndFile = GetSoundFile();
  4452. if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
  4453. {
  4454. return;
  4455. }
  4456. PrepareUndo(m_Cursor, m_Cursor, "Instrument Entry");
  4457. ModCommand &target = GetCursorCommand();
  4458. ModCommand oldcmd = target; // This is the command we are about to overwrite
  4459. UINT instr = target.instr, nTotalMax, nTempMax;
  4460. if(target.IsPcNote()) // this is a plugin index
  4461. {
  4462. nTotalMax = MAX_MIXPLUGINS + 1;
  4463. nTempMax = MAX_MIXPLUGINS + 1;
  4464. } else if(pSndFile->GetNumInstruments() > 0) // this is an instrument index
  4465. {
  4466. nTotalMax = MAX_INSTRUMENTS;
  4467. nTempMax = pSndFile->GetNumInstruments();
  4468. } else
  4469. {
  4470. nTotalMax = MAX_SAMPLES;
  4471. nTempMax = pSndFile->GetNumSamples();
  4472. }
  4473. instr = ((instr * 10) + val) % 1000;
  4474. if(instr >= nTotalMax)
  4475. instr = instr % 100;
  4476. if(nTempMax < 100) // if we're using samples & have less than 100 samples
  4477. instr = instr % 100; // or if we're using instruments and have less than 100 instruments
  4478. // --> ensure the entered instrument value is less than 100.
  4479. target.instr = static_cast<ModCommand::INSTR>(instr);
  4480. SetSelToCursor();
  4481. if(target != oldcmd)
  4482. {
  4483. SetModified(false);
  4484. InvalidateCell(m_Cursor);
  4485. UpdateIndicator();
  4486. }
  4487. if(target.IsPcNote())
  4488. {
  4489. m_PCNoteEditMemory = target;
  4490. }
  4491. }
  4492. // Enter a note in the pattern
  4493. void CViewPattern::TempEnterNote(ModCommand::NOTE note, int vol, bool fromMidi)
  4494. {
  4495. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  4496. CModDoc *pModDoc = GetDocument();
  4497. if(pMainFrm == nullptr || pModDoc == nullptr)
  4498. {
  4499. return;
  4500. }
  4501. CSoundFile &sndFile = pModDoc->GetSoundFile();
  4502. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  4503. {
  4504. return;
  4505. }
  4506. if(note < NOTE_MIN_SPECIAL)
  4507. {
  4508. Limit(note, sndFile.GetModSpecifications().noteMin, sndFile.GetModSpecifications().noteMax);
  4509. }
  4510. // Special case: Convert note off commands to C00 for MOD files
  4511. if((sndFile.GetType() == MOD_TYPE_MOD) && (note == NOTE_NOTECUT || note == NOTE_FADE || note == NOTE_KEYOFF))
  4512. {
  4513. TempEnterFX(CMD_VOLUME, 0);
  4514. return;
  4515. }
  4516. // Check whether the module format supports the note.
  4517. if(sndFile.GetModSpecifications().HasNote(note) == false)
  4518. {
  4519. return;
  4520. }
  4521. const bool liveRecord = IsLiveRecord();
  4522. const bool usePlaybackPosition = (liveRecord && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_AUTODELAY) && !sndFile.m_SongFlags[SONG_STEP]);
  4523. const bool isSpecial = note >= NOTE_MIN_SPECIAL;
  4524. const bool isSplit = IsNoteSplit(note);
  4525. PatternEditPos editPos = GetEditPos(sndFile, usePlaybackPosition);
  4526. const bool recordEnabled = IsEditingEnabled();
  4527. CHANNELINDEX nChn = GetCurrentChannel();
  4528. auto recordGroup = pModDoc->GetChannelRecordGroup(nChn);
  4529. if(!isSpecial && pModDoc->GetSplitKeyboardSettings().IsSplitActive()
  4530. && ((recordGroup == RecordGroup::Group1 && isSplit) || (recordGroup == RecordGroup::Group2 && !isSplit)))
  4531. {
  4532. // Record group 1 should be used for normal notes, record group 2 for split notes.
  4533. // If there are any channels assigned to the "other" record group, we switch to another channel.
  4534. auto otherGroup = (recordGroup == RecordGroup::Group1) ? RecordGroup::Group2 : RecordGroup::Group1;
  4535. const CHANNELINDEX newChannel = FindGroupRecordChannel(otherGroup, true);
  4536. if(newChannel != CHANNELINDEX_INVALID)
  4537. {
  4538. // Found a free channel, switch to other record group.
  4539. nChn = newChannel;
  4540. recordGroup = otherGroup;
  4541. }
  4542. }
  4543. // -- Chord autodetection: step back if we just entered a note
  4544. if(recordEnabled && recordGroup != RecordGroup::NoGroup && !liveRecord && !ModCommand::IsPcNote(note) && m_nSpacing > 0)
  4545. {
  4546. const auto &order = Order();
  4547. if((timeGetTime() - m_autoChordStartTime) < TrackerSettings::Instance().gnAutoChordWaitTime
  4548. && order.IsValidPat(m_autoChordStartOrder)
  4549. && sndFile.Patterns[order[m_autoChordStartOrder]].IsValidRow(m_autoChordStartRow))
  4550. {
  4551. const auto pattern = order[m_autoChordStartOrder];
  4552. if(pattern != editPos.pattern)
  4553. {
  4554. SetCurrentOrder(m_autoChordStartOrder);
  4555. SetCurrentPattern(pattern, m_autoChordStartRow);
  4556. }
  4557. editPos.pattern = pattern;
  4558. editPos.row = m_autoChordStartRow;
  4559. } else
  4560. {
  4561. m_autoChordStartRow = ROWINDEX_INVALID;
  4562. m_autoChordStartOrder = ORDERINDEX_INVALID;
  4563. }
  4564. m_autoChordStartTime = timeGetTime();
  4565. if(m_autoChordStartOrder == ORDERINDEX_INVALID || m_autoChordStartRow == ROWINDEX_INVALID)
  4566. {
  4567. m_autoChordStartOrder = editPos.order;
  4568. m_autoChordStartRow = editPos.row;
  4569. }
  4570. }
  4571. // Quantize
  4572. const bool doQuantize = (liveRecord || (fromMidi && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))) && TrackerSettings::Instance().recordQuantizeRows != 0;
  4573. if(doQuantize)
  4574. {
  4575. QuantizeRow(editPos.pattern, editPos.row);
  4576. // "Grace notes" are stuffed into the next row, if possible
  4577. if(sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn)->IsNote() && editPos.row < sndFile.Patterns[editPos.pattern].GetNumRows() - 1)
  4578. {
  4579. editPos.row++;
  4580. }
  4581. }
  4582. // -- Work out where to put the new note
  4583. ModCommand *pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
  4584. ModCommand newcmd = *pTarget;
  4585. // Param control 'note'
  4586. if(ModCommand::IsPcNote(note))
  4587. {
  4588. if(!pTarget->IsPcNote())
  4589. {
  4590. // We're overwriting a normal cell with a PC note.
  4591. newcmd = m_PCNoteEditMemory;
  4592. if((pTarget->command == CMD_MIDI || pTarget->command == CMD_SMOOTHMIDI) && pTarget->param < 128)
  4593. {
  4594. newcmd.SetValueEffectCol(static_cast<decltype(newcmd.GetValueEffectCol())>(Util::muldivr(pTarget->param, ModCommand::maxColumnValue, 127)));
  4595. if(!newcmd.instr)
  4596. newcmd.instr = sndFile.ChnSettings[nChn].nMixPlugin;
  4597. auto activeMacro = sndFile.m_PlayState.Chn[nChn].nActiveMacro;
  4598. if(!newcmd.GetValueVolCol() && sndFile.m_MidiCfg.GetParameteredMacroType(activeMacro) == kSFxPlugParam)
  4599. {
  4600. PlugParamIndex plugParam = sndFile.m_MidiCfg.MacroToPlugParam(sndFile.m_PlayState.Chn[nChn].nActiveMacro);
  4601. if(plugParam < ModCommand::maxColumnValue)
  4602. newcmd.SetValueVolCol(static_cast<decltype(newcmd.GetValueVolCol())>(plugParam));
  4603. }
  4604. }
  4605. } else if(recordEnabled)
  4606. {
  4607. // Pick up current entry to update PC note edit memory.
  4608. m_PCNoteEditMemory = newcmd;
  4609. }
  4610. newcmd.note = note;
  4611. } else
  4612. {
  4613. // Are we overwriting a PC note here?
  4614. if(pTarget->IsPcNote())
  4615. {
  4616. newcmd.Clear();
  4617. }
  4618. // -- write note and instrument data.
  4619. HandleSplit(newcmd, note);
  4620. // Nice idea actually: Use lower section of the keyboard to play chords (but it won't work 100% correctly this way...)
  4621. /*if(isSplit)
  4622. {
  4623. TempEnterChord(note);
  4624. return;
  4625. }*/
  4626. // -- write vol data
  4627. int volWrite = -1;
  4628. if(vol >= 0 && vol <= 64 && !(isSplit && pModDoc->GetSplitKeyboardSettings().splitVolume)) //write valid volume, as long as there's no split volume override.
  4629. {
  4630. volWrite = vol;
  4631. } else if(isSplit && pModDoc->GetSplitKeyboardSettings().splitVolume) //cater for split volume override.
  4632. {
  4633. if(pModDoc->GetSplitKeyboardSettings().splitVolume > 0 && pModDoc->GetSplitKeyboardSettings().splitVolume <= 64)
  4634. {
  4635. volWrite = pModDoc->GetSplitKeyboardSettings().splitVolume;
  4636. }
  4637. }
  4638. if(volWrite != -1 && !isSpecial)
  4639. {
  4640. if(sndFile.GetModSpecifications().HasVolCommand(VOLCMD_VOLUME))
  4641. {
  4642. newcmd.volcmd = VOLCMD_VOLUME;
  4643. newcmd.vol = (ModCommand::VOL)volWrite;
  4644. } else
  4645. {
  4646. newcmd.command = CMD_VOLUME;
  4647. newcmd.param = (ModCommand::PARAM)volWrite;
  4648. }
  4649. }
  4650. // -- write sdx if playing live
  4651. if(usePlaybackPosition && m_nPlayTick && !doQuantize) // avoid SD0 which will be mis-interpreted
  4652. {
  4653. if(newcmd.command == CMD_NONE) //make sure we don't overwrite any existing commands.
  4654. {
  4655. newcmd.command = CMD_S3MCMDEX;
  4656. if(!sndFile.GetModSpecifications().HasCommand(CMD_S3MCMDEX))
  4657. newcmd.command = CMD_MODCMDEX;
  4658. uint8 maxSpeed = 0x0F;
  4659. if(m_nTicksOnRow > 0)
  4660. maxSpeed = std::min(uint8(0x0F), mpt::saturate_cast<uint8>(m_nTicksOnRow - 1));
  4661. newcmd.param = static_cast<ModCommand::PARAM>(0xD0 | std::min(maxSpeed, mpt::saturate_cast<uint8>(m_nPlayTick)));
  4662. }
  4663. }
  4664. // Note cut/off/fade: erase instrument number
  4665. if(newcmd.note >= NOTE_MIN_SPECIAL)
  4666. newcmd.instr = 0;
  4667. }
  4668. // -- if recording, create undo point and write out modified command.
  4669. const bool modified = (recordEnabled && *pTarget != newcmd);
  4670. if(modified)
  4671. {
  4672. pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, nChn, editPos.row, 1, 1, "Note Entry");
  4673. *pTarget = newcmd;
  4674. }
  4675. // -- play note
  4676. if(((TrackerSettings::Instance().m_dwPatternSetup & (PATTERN_PLAYNEWNOTE | PATTERN_PLAYEDITROW)) || !recordEnabled) && !newcmd.IsPcNote())
  4677. {
  4678. const bool playWholeRow = ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !liveRecord);
  4679. if(playWholeRow)
  4680. {
  4681. // play the whole row in "step mode"
  4682. PatternStep(editPos.row);
  4683. if(recordEnabled && newcmd.IsNote())
  4684. m_noteChannel[newcmd.note - NOTE_MIN] = nChn;
  4685. }
  4686. if(!playWholeRow || !recordEnabled)
  4687. {
  4688. // NOTE: This code is *also* used for the PATTERN_PLAYEDITROW edit mode because of some unforseeable race conditions when modifying pattern data.
  4689. // We have to use this code when editing is disabled or else we will get some stupid hazards, because we would first have to write the new note
  4690. // data to the pattern and then remove it again - but often, it is actually removed before the row is parsed by the soundlib.
  4691. // just play the newly inserted note using the already specified instrument...
  4692. ModCommand::INSTR playIns = newcmd.instr;
  4693. if(!playIns && ModCommand::IsNoteOrEmpty(note))
  4694. {
  4695. // ...or one that can be found on a previous row of this pattern.
  4696. ModCommand *search = pTarget;
  4697. ROWINDEX srow = editPos.row;
  4698. while(srow-- > 0)
  4699. {
  4700. search -= sndFile.GetNumChannels();
  4701. if(search->instr && !search->IsPcNote())
  4702. {
  4703. playIns = search->instr;
  4704. m_fallbackInstrument = playIns; //used to figure out which instrument to stop on key release.
  4705. break;
  4706. }
  4707. }
  4708. }
  4709. PlayNote(newcmd.note, playIns, 4 * vol, nChn);
  4710. }
  4711. }
  4712. if(newcmd.IsNote())
  4713. {
  4714. m_previousNote[nChn] = note;
  4715. }
  4716. // -- if recording, handle post note entry behaviour (move cursor etc..)
  4717. if(recordEnabled)
  4718. {
  4719. PatternCursor sel(editPos.row, nChn, m_Cursor.GetColumnType());
  4720. if(!liveRecord)
  4721. {
  4722. // Update only when not recording live.
  4723. SetCurSel(sel);
  4724. }
  4725. if(modified) // Has it really changed?
  4726. {
  4727. SetModified(false);
  4728. if(editPos.pattern == m_nPattern)
  4729. InvalidateCell(sel);
  4730. else
  4731. InvalidatePattern();
  4732. if(!liveRecord)
  4733. {
  4734. // Update only when not recording live.
  4735. UpdateIndicator();
  4736. }
  4737. }
  4738. // Set new cursor position (edit step aka row spacing)
  4739. if(!liveRecord)
  4740. {
  4741. if(m_nSpacing > 0)
  4742. {
  4743. if(editPos.row + m_nSpacing < sndFile.Patterns[editPos.pattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
  4744. {
  4745. SetCurrentRow(editPos.row + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
  4746. }
  4747. }
  4748. SetSelToCursor();
  4749. }
  4750. if(newcmd.IsPcNote())
  4751. {
  4752. // Nothing to do here anymore.
  4753. return;
  4754. }
  4755. auto &activeNoteMap = isSplit ? m_splitActiveNoteChannel : m_activeNoteChannel;
  4756. if(newcmd.note <= NOTE_MAX)
  4757. activeNoteMap[newcmd.note] = static_cast<decltype(m_activeNoteChannel)::value_type>(nChn);
  4758. if(recordGroup != RecordGroup::NoGroup)
  4759. {
  4760. // Move to next channel in record group
  4761. nChn = FindGroupRecordChannel(recordGroup, false, nChn + 1);
  4762. if(nChn != CHANNELINDEX_INVALID)
  4763. {
  4764. SetCurrentColumn(nChn);
  4765. }
  4766. }
  4767. }
  4768. }
  4769. void CViewPattern::PlayNote(ModCommand::NOTE note, ModCommand::INSTR instr, int volume, CHANNELINDEX channel)
  4770. {
  4771. CModDoc *modDoc = GetDocument();
  4772. modDoc->PlayNote(PlayNoteParam(note).Instrument(instr).Volume(volume).Channel(channel).CheckNNA(m_baPlayingNote), &m_noteChannel);
  4773. }
  4774. void CViewPattern::PreviewNote(ROWINDEX row, CHANNELINDEX channel)
  4775. {
  4776. const ModCommand &m = *GetSoundFile()->Patterns[m_nPattern].GetpModCommand(row, channel);
  4777. if(m.IsNote() && m.instr)
  4778. {
  4779. int vol = -1;
  4780. if(m.command == CMD_VOLUME)
  4781. vol = m.param * 4u;
  4782. else if(m.volcmd == VOLCMD_VOLUME)
  4783. vol = m.vol * 4u;
  4784. // Note-off any previews from this channel first
  4785. ModCommand::NOTE note = NOTE_MIN;
  4786. const auto &channels = GetSoundFile()->m_PlayState.Chn;
  4787. for(auto &chn : m_noteChannel)
  4788. {
  4789. if(chn != CHANNELINDEX_INVALID && channels[chn].isPreviewNote && channels[chn].nMasterChn == channel + 1)
  4790. {
  4791. GetDocument()->NoteOff(note, false, m.instr, chn);
  4792. }
  4793. note++;
  4794. }
  4795. PlayNote(m.note, m.instr, vol, channel);
  4796. }
  4797. }
  4798. // Construct a chord from the chord presets. Returns number of notes in chord.
  4799. int CViewPattern::ConstructChord(int note, ModCommand::NOTE (&outNotes)[MPTChord::notesPerChord], ModCommand::NOTE baseNote)
  4800. {
  4801. const MPTChords &chords = TrackerSettings::GetChords();
  4802. UINT chordNum = note - GetBaseNote();
  4803. if(chordNum >= chords.size())
  4804. {
  4805. return 0;
  4806. }
  4807. const MPTChord &chord = chords[chordNum];
  4808. const bool relativeMode = (chord.key == MPTChord::relativeMode); // Notes are relative to a previously entered note in the pattern
  4809. ModCommand::NOTE key;
  4810. if(relativeMode)
  4811. {
  4812. // Relative mode: Use pattern note as base note.
  4813. // If there is no valid note in the pattern: Use shortcut note as relative base note
  4814. key = ModCommand::IsNote(baseNote) ? baseNote : static_cast<ModCommand::NOTE>(note);
  4815. } else
  4816. {
  4817. // Default mode: Use base key
  4818. key = GetNoteWithBaseOctave(chord.key);
  4819. }
  4820. if(!ModCommand::IsNote(key))
  4821. {
  4822. return 0;
  4823. }
  4824. int numNotes = 0;
  4825. const CModSpecifications &specs = GetSoundFile()->GetModSpecifications();
  4826. if(specs.HasNote(key))
  4827. {
  4828. outNotes[numNotes++] = key;
  4829. }
  4830. int32 baseKey = key - NOTE_MIN;
  4831. if(!relativeMode)
  4832. {
  4833. // Only use octave information from the base key
  4834. baseKey = (baseKey / 12) * 12;
  4835. }
  4836. for(auto cnote : chord.notes)
  4837. {
  4838. if(cnote != MPTChord::noNote)
  4839. {
  4840. int32 chordNote = baseKey + cnote + NOTE_MIN;
  4841. if(chordNote >= NOTE_MIN && chordNote <= NOTE_MAX && specs.HasNote(static_cast<ModCommand::NOTE>(chordNote)))
  4842. {
  4843. outNotes[numNotes++] = static_cast<ModCommand::NOTE>(chordNote);
  4844. }
  4845. }
  4846. }
  4847. return numNotes;
  4848. }
  4849. // Enter a chord in the pattern
  4850. void CViewPattern::TempEnterChord(ModCommand::NOTE note)
  4851. {
  4852. CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
  4853. CModDoc *pModDoc = GetDocument();
  4854. if(pMainFrm == nullptr || pModDoc == nullptr)
  4855. {
  4856. return;
  4857. }
  4858. CSoundFile &sndFile = pModDoc->GetSoundFile();
  4859. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  4860. {
  4861. return;
  4862. }
  4863. const CHANNELINDEX chn = GetCurrentChannel();
  4864. const PatternRow rowBase = sndFile.Patterns[m_nPattern].GetRow(GetCurrentRow());
  4865. ModCommand::NOTE chordNotes[MPTChord::notesPerChord], baseNote = rowBase[chn].note;
  4866. if(!ModCommand::IsNote(baseNote))
  4867. {
  4868. baseNote = m_prevChordBaseNote;
  4869. }
  4870. int numNotes = ConstructChord(note, chordNotes, baseNote);
  4871. if(!numNotes)
  4872. {
  4873. return;
  4874. }
  4875. // Save old row contents
  4876. std::vector<ModCommand> newRow(rowBase, rowBase + sndFile.GetNumChannels());
  4877. const bool liveRecord = IsLiveRecord();
  4878. const bool recordEnabled = IsEditingEnabled();
  4879. bool modified = false;
  4880. // -- establish note data
  4881. HandleSplit(newRow[chn], note);
  4882. const auto recordGroup = pModDoc->GetChannelRecordGroup(chn);
  4883. CHANNELINDEX curChn = chn;
  4884. for(int i = 0; i < numNotes; i++)
  4885. {
  4886. // Find appropriate channel
  4887. while(curChn < sndFile.GetNumChannels() && pModDoc->GetChannelRecordGroup(curChn) != recordGroup)
  4888. {
  4889. curChn++;
  4890. }
  4891. if(curChn >= sndFile.GetNumChannels())
  4892. {
  4893. numNotes = i;
  4894. break;
  4895. }
  4896. m_chordPatternChannels[i] = curChn;
  4897. ModCommand &m = newRow[curChn];
  4898. m_previousNote[curChn] = m.note = chordNotes[i];
  4899. if(newRow[chn].instr)
  4900. {
  4901. m.instr = newRow[chn].instr;
  4902. }
  4903. if(rowBase[chn] != m)
  4904. {
  4905. modified = true;
  4906. }
  4907. curChn++;
  4908. }
  4909. m_Status.set(psChordPlaying);
  4910. // -- write notedata
  4911. if(recordEnabled)
  4912. {
  4913. SetSelToCursor();
  4914. if(modified)
  4915. {
  4916. // Simply backup the whole row.
  4917. pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, chn, GetCurrentRow(), sndFile.GetNumChannels(), 1, "Chord Entry");
  4918. for(CHANNELINDEX n = 0; n < sndFile.GetNumChannels(); n++)
  4919. {
  4920. rowBase[n] = newRow[n];
  4921. }
  4922. SetModified(false);
  4923. InvalidateRow();
  4924. UpdateIndicator();
  4925. }
  4926. }
  4927. // -- play note
  4928. if((TrackerSettings::Instance().m_dwPatternSetup & (PATTERN_PLAYNEWNOTE | PATTERN_PLAYEDITROW)) || !recordEnabled)
  4929. {
  4930. if(m_prevChordNote != NOTE_NONE)
  4931. {
  4932. TempStopChord(m_prevChordNote);
  4933. }
  4934. const bool playWholeRow = ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !liveRecord);
  4935. if(playWholeRow)
  4936. {
  4937. // play the whole row in "step mode"
  4938. PatternStep(GetCurrentRow());
  4939. if(recordEnabled)
  4940. {
  4941. for(int i = 0; i < numNotes; i++)
  4942. {
  4943. m_noteChannel[chordNotes[i] - NOTE_MIN] = m_chordPatternChannels[i];
  4944. }
  4945. }
  4946. }
  4947. if(!playWholeRow || !recordEnabled)
  4948. {
  4949. // NOTE: This code is *also* used for the PATTERN_PLAYEDITROW edit mode because of some unforseeable race conditions when modifying pattern data.
  4950. // We have to use this code when editing is disabled or else we will get some stupid hazards, because we would first have to write the new note
  4951. // data to the pattern and then remove it again - but often, it is actually removed before the row is parsed by the soundlib.
  4952. // just play the newly inserted notes...
  4953. const ModCommand &firstNote = rowBase[chn];
  4954. ModCommand::INSTR playIns = 0;
  4955. if(firstNote.instr)
  4956. {
  4957. // ...using the already specified instrument
  4958. playIns = firstNote.instr;
  4959. } else if(!firstNote.instr)
  4960. {
  4961. // ...or one that can be found on a previous row of this pattern.
  4962. const ModCommand *search = &firstNote;
  4963. ROWINDEX srow = GetCurrentRow();
  4964. while(srow-- > 0)
  4965. {
  4966. search -= sndFile.GetNumChannels();
  4967. if(search->instr)
  4968. {
  4969. playIns = search->instr;
  4970. m_fallbackInstrument = playIns; //used to figure out which instrument to stop on key release.
  4971. break;
  4972. }
  4973. }
  4974. }
  4975. for(int i = 0; i < numNotes; i++)
  4976. {
  4977. pModDoc->PlayNote(PlayNoteParam(chordNotes[i]).Instrument(playIns).Channel(chn).CheckNNA(m_baPlayingNote), &m_noteChannel);
  4978. }
  4979. }
  4980. } // end play note
  4981. m_prevChordNote = note;
  4982. m_prevChordBaseNote = baseNote;
  4983. // Set new cursor position (edit step aka row spacing) - only when not recording live
  4984. if(recordEnabled && !liveRecord)
  4985. {
  4986. if(m_nSpacing > 0)
  4987. {
  4988. // Shift from entering chord may have triggered this flag, which will prevent us from wrapping to the next pattern.
  4989. m_Status.reset(psKeyboardDragSelect);
  4990. SetCurrentRow(GetCurrentRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
  4991. }
  4992. SetSelToCursor();
  4993. }
  4994. }
  4995. // Translate incoming MIDI aftertouch messages to pattern commands
  4996. void CViewPattern::EnterAftertouch(ModCommand::NOTE note, int atValue)
  4997. {
  4998. if(TrackerSettings::Instance().aftertouchBehaviour == atDoNotRecord || !IsEditingEnabled())
  4999. return;
  5000. const CHANNELINDEX numChannels = GetSoundFile()->GetNumChannels();
  5001. std::set<CHANNELINDEX> channels;
  5002. if(ModCommand::IsNote(note))
  5003. {
  5004. // For polyphonic aftertouch, map the aftertouch note to the correct pattern channel.
  5005. const auto &activeNoteMap = IsNoteSplit(note) ? m_splitActiveNoteChannel : m_activeNoteChannel;
  5006. if(activeNoteMap[note] < numChannels)
  5007. {
  5008. channels.insert(activeNoteMap[note]);
  5009. } else
  5010. {
  5011. // Couldn't find the channel that belongs to this note... Don't bother writing aftertouch messages.
  5012. // This is actually necessary, because it is possible that the last aftertouch message for a note
  5013. // is received after the key-off event, in which case OpenMPT won't know anymore on which channel
  5014. // that particular note was, so it will just put the message on some other channel. We don't want that!
  5015. return;
  5016. }
  5017. } else
  5018. {
  5019. for(const auto &noteMap : { m_activeNoteChannel, m_splitActiveNoteChannel })
  5020. {
  5021. for(const auto chn : noteMap)
  5022. {
  5023. if(chn < numChannels)
  5024. channels.insert(chn);
  5025. }
  5026. }
  5027. if(channels.empty())
  5028. channels.insert(m_Cursor.GetChannel());
  5029. }
  5030. Limit(atValue, 0, 127);
  5031. const PatternCursor endOfRow{ m_Cursor.GetRow(), static_cast<CHANNELINDEX>(numChannels - 1u), PatternCursor::lastColumn };
  5032. const auto &specs = GetSoundFile()->GetModSpecifications();
  5033. bool first = true, modified = false;
  5034. for(const auto chn : channels)
  5035. {
  5036. const PatternCursor cursor{ m_Cursor.GetRow(), chn };
  5037. ModCommand &target = GetModCommand(cursor);
  5038. ModCommand newCommand = target;
  5039. if(target.IsPcNote())
  5040. continue;
  5041. switch(TrackerSettings::Instance().aftertouchBehaviour)
  5042. {
  5043. case atRecordAsVolume:
  5044. // Record aftertouch messages as volume commands
  5045. if(specs.HasVolCommand(VOLCMD_VOLUME))
  5046. {
  5047. if(newCommand.volcmd == VOLCMD_NONE || newCommand.volcmd == VOLCMD_VOLUME)
  5048. {
  5049. newCommand.volcmd = VOLCMD_VOLUME;
  5050. newCommand.vol = static_cast<ModCommand::VOL>((atValue * 64 + 64) / 127);
  5051. }
  5052. } else if(specs.HasCommand(CMD_VOLUME))
  5053. {
  5054. if(newCommand.command == CMD_NONE || newCommand.command == CMD_VOLUME)
  5055. {
  5056. newCommand.command = CMD_VOLUME;
  5057. newCommand.param = static_cast<ModCommand::PARAM>((atValue * 64 + 64) / 127);
  5058. }
  5059. }
  5060. break;
  5061. case atRecordAsMacro:
  5062. // Record aftertouch messages as MIDI Macros
  5063. if(newCommand.command == CMD_NONE || newCommand.command == CMD_SMOOTHMIDI || newCommand.command == CMD_MIDI)
  5064. {
  5065. auto cmd =
  5066. specs.HasCommand(CMD_SMOOTHMIDI) ? CMD_SMOOTHMIDI :
  5067. specs.HasCommand(CMD_MIDI) ? CMD_MIDI :
  5068. CMD_NONE;
  5069. if(cmd != CMD_NONE)
  5070. {
  5071. newCommand.command = static_cast<ModCommand::COMMAND>(cmd);
  5072. newCommand.param = static_cast<ModCommand::PARAM>(atValue);
  5073. }
  5074. }
  5075. break;
  5076. }
  5077. if(target != newCommand)
  5078. {
  5079. if(first)
  5080. PrepareUndo(cursor, endOfRow, "Aftertouch Entry");
  5081. first = false;
  5082. modified = true;
  5083. target = newCommand;
  5084. InvalidateCell(cursor);
  5085. }
  5086. }
  5087. if(modified)
  5088. {
  5089. SetModified(false);
  5090. UpdateIndicator();
  5091. }
  5092. }
  5093. // Apply quantization factor to given row.
  5094. void CViewPattern::QuantizeRow(PATTERNINDEX &pat, ROWINDEX &row) const
  5095. {
  5096. const CSoundFile *sndFile = GetSoundFile();
  5097. if(sndFile == nullptr || TrackerSettings::Instance().recordQuantizeRows == 0)
  5098. {
  5099. return;
  5100. }
  5101. const ROWINDEX currentTick = m_nTicksOnRow * row + m_nPlayTick;
  5102. const ROWINDEX ticksPerNote = TrackerSettings::Instance().recordQuantizeRows * m_nTicksOnRow;
  5103. // Previous quantization step
  5104. const ROWINDEX quantLow = (currentTick / ticksPerNote) * ticksPerNote;
  5105. // Next quantization step
  5106. const ROWINDEX quantHigh = (1 + (currentTick / ticksPerNote)) * ticksPerNote;
  5107. if(currentTick - quantLow < quantHigh - currentTick)
  5108. {
  5109. row = quantLow / m_nTicksOnRow;
  5110. } else
  5111. {
  5112. row = quantHigh / m_nTicksOnRow;
  5113. }
  5114. if(!sndFile->Patterns[pat].IsValidRow(row))
  5115. {
  5116. // Quantization exceeds current pattern, try stuffing note into next pattern instead.
  5117. PATTERNINDEX nextPat = sndFile->m_SongFlags[SONG_PATTERNLOOP] ? m_nPattern : GetNextPattern();
  5118. if(nextPat != PATTERNINDEX_INVALID)
  5119. {
  5120. pat = nextPat;
  5121. row = 0;
  5122. } else
  5123. {
  5124. row = sndFile->Patterns[pat].GetNumRows() - 1;
  5125. }
  5126. }
  5127. }
  5128. // Get previous pattern in order list
  5129. PATTERNINDEX CViewPattern::GetPrevPattern() const
  5130. {
  5131. const CSoundFile *sndFile = GetSoundFile();
  5132. if(sndFile != nullptr)
  5133. {
  5134. const auto &order = Order();
  5135. const ORDERINDEX curOrder = GetCurrentOrder();
  5136. if(curOrder > 0 && m_nPattern == order[curOrder])
  5137. {
  5138. const ORDERINDEX nextOrder = order.GetPreviousOrderIgnoringSkips(curOrder);
  5139. const PATTERNINDEX nextPat = order[nextOrder];
  5140. if(sndFile->Patterns.IsValidPat(nextPat) && sndFile->Patterns[nextPat].GetNumRows())
  5141. {
  5142. return nextPat;
  5143. }
  5144. }
  5145. }
  5146. return PATTERNINDEX_INVALID;
  5147. }
  5148. // Get follow-up pattern in order list
  5149. PATTERNINDEX CViewPattern::GetNextPattern() const
  5150. {
  5151. const CSoundFile *sndFile = GetSoundFile();
  5152. if(sndFile != nullptr)
  5153. {
  5154. const auto &order = Order();
  5155. const ORDERINDEX curOrder = GetCurrentOrder();
  5156. if(curOrder + 1 < order.GetLength() && m_nPattern == order[curOrder])
  5157. {
  5158. const ORDERINDEX nextOrder = order.GetNextOrderIgnoringSkips(curOrder);
  5159. const PATTERNINDEX nextPat = order[nextOrder];
  5160. if(sndFile->Patterns.IsValidPat(nextPat) && sndFile->Patterns[nextPat].GetNumRows())
  5161. {
  5162. return nextPat;
  5163. }
  5164. }
  5165. }
  5166. return PATTERNINDEX_INVALID;
  5167. }
  5168. void CViewPattern::OnSetQuantize()
  5169. {
  5170. CInputDlg dlg(this, _T("Quantize amount in rows for live recording (0 to disable):"), 0, MAX_PATTERN_ROWS, TrackerSettings::Instance().recordQuantizeRows);
  5171. if(dlg.DoModal())
  5172. {
  5173. TrackerSettings::Instance().recordQuantizeRows = static_cast<ROWINDEX>(dlg.resultAsInt);
  5174. }
  5175. }
  5176. void CViewPattern::OnLockPatternRows()
  5177. {
  5178. CSoundFile &sndFile = *GetSoundFile();
  5179. if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
  5180. {
  5181. sndFile.m_lockRowStart = m_Selection.GetStartRow();
  5182. sndFile.m_lockRowEnd = m_Selection.GetEndRow();
  5183. } else
  5184. {
  5185. sndFile.m_lockRowStart = sndFile.m_lockRowEnd = ROWINDEX_INVALID;
  5186. }
  5187. InvalidatePattern(true, true);
  5188. }
  5189. // Find a free channel for a record group, starting search from a given channel.
  5190. // If forceFreeChannel is true and all channels in the specified record group are active, some channel is picked from the specified record group.
  5191. CHANNELINDEX CViewPattern::FindGroupRecordChannel(RecordGroup recordGroup, bool forceFreeChannel, CHANNELINDEX startChannel) const
  5192. {
  5193. const CModDoc *pModDoc = GetDocument();
  5194. if(pModDoc == nullptr)
  5195. return CHANNELINDEX_INVALID;
  5196. CHANNELINDEX chn = startChannel;
  5197. CHANNELINDEX foundChannel = CHANNELINDEX_INVALID;
  5198. for(CHANNELINDEX i = 1; i < pModDoc->GetNumChannels(); i++, chn++)
  5199. {
  5200. if(chn >= pModDoc->GetNumChannels())
  5201. chn = 0; // loop around
  5202. if(pModDoc->GetChannelRecordGroup(chn) == recordGroup)
  5203. {
  5204. // Check if any notes are playing on this channel
  5205. bool channelLocked = false;
  5206. for(size_t k = 0; k < m_activeNoteChannel.size(); k++)
  5207. {
  5208. if(m_activeNoteChannel[k] == chn || m_splitActiveNoteChannel[k] == chn)
  5209. {
  5210. channelLocked = true;
  5211. break;
  5212. }
  5213. }
  5214. if(!channelLocked)
  5215. {
  5216. // Channel belongs to correct record group and no note is currently playing.
  5217. return chn;
  5218. }
  5219. if(forceFreeChannel)
  5220. {
  5221. // If all channels are active, we might still pick a random channel from the specified group.
  5222. foundChannel = chn;
  5223. }
  5224. }
  5225. }
  5226. return foundChannel;
  5227. }
  5228. void CViewPattern::OnClearField(const RowMask &mask, bool step, bool ITStyle)
  5229. {
  5230. CSoundFile *sndFile = GetSoundFile();
  5231. if(sndFile == nullptr || !IsEditingEnabled_bmsg())
  5232. return;
  5233. // If we have a selection, we want to do something different
  5234. if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
  5235. {
  5236. OnClearSelection(ITStyle);
  5237. return;
  5238. }
  5239. PrepareUndo(m_Cursor, m_Cursor, "Clear Field");
  5240. ModCommand &target = GetCursorCommand();
  5241. ModCommand oldcmd = target;
  5242. if(mask.note)
  5243. {
  5244. // Clear note
  5245. if(target.IsPcNote())
  5246. {
  5247. // Need to clear entire field if this is a PC Event.
  5248. target.Clear();
  5249. } else
  5250. {
  5251. target.note = NOTE_NONE;
  5252. if(ITStyle)
  5253. {
  5254. target.instr = 0;
  5255. }
  5256. }
  5257. }
  5258. if(mask.instrument)
  5259. {
  5260. // Clear instrument
  5261. target.instr = 0;
  5262. }
  5263. if(mask.volume)
  5264. {
  5265. // Clear volume effect
  5266. target.volcmd = VOLCMD_NONE;
  5267. target.vol = 0;
  5268. }
  5269. if(mask.command)
  5270. {
  5271. // Clear effect command
  5272. target.command = CMD_NONE;
  5273. }
  5274. if(mask.parameter)
  5275. {
  5276. // Clear effect parameter
  5277. target.param = 0;
  5278. }
  5279. if((mask.command || mask.parameter) && (target.IsPcNote()))
  5280. {
  5281. target.SetValueEffectCol(0);
  5282. }
  5283. SetSelToCursor();
  5284. if(target != oldcmd)
  5285. {
  5286. SetModified(false);
  5287. InvalidateRow();
  5288. UpdateIndicator();
  5289. }
  5290. if(step && (sndFile->IsPaused() || !m_Status[psFollowSong] ||
  5291. (CMainFrame::GetMainFrame() != nullptr && CMainFrame::GetMainFrame()->GetFollowSong(GetDocument()) != m_hWnd)))
  5292. {
  5293. // Preview Row
  5294. if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !IsLiveRecord())
  5295. {
  5296. PatternStep(GetCurrentRow());
  5297. }
  5298. if(m_nSpacing > 0)
  5299. SetCurrentRow(GetCurrentRow() + m_nSpacing);
  5300. SetSelToCursor();
  5301. }
  5302. }
  5303. void CViewPattern::OnInitMenu(CMenu *pMenu)
  5304. {
  5305. CModScrollView::OnInitMenu(pMenu);
  5306. }
  5307. void CViewPattern::TogglePluginEditor(int chan)
  5308. {
  5309. CModDoc *modDoc = GetDocument();
  5310. if(!modDoc)
  5311. return;
  5312. int plug = modDoc->GetSoundFile().ChnSettings[chan].nMixPlugin;
  5313. if(plug > 0)
  5314. modDoc->TogglePluginEditor(plug - 1);
  5315. return;
  5316. }
  5317. void CViewPattern::OnSelectInstrument(UINT nID)
  5318. {
  5319. SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(nID - ID_CHANGE_INSTRUMENT), true);
  5320. }
  5321. void CViewPattern::OnSelectPCNoteParam(UINT nID)
  5322. {
  5323. CSoundFile *sndFile = GetSoundFile();
  5324. if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
  5325. return;
  5326. uint16 paramNdx = static_cast<uint16>(nID - ID_CHANGE_PCNOTE_PARAM);
  5327. bool modified = false;
  5328. ApplyToSelection([paramNdx, &modified] (ModCommand &m, ROWINDEX, CHANNELINDEX)
  5329. {
  5330. if(m.IsPcNote() && (m.GetValueVolCol() != paramNdx))
  5331. {
  5332. m.SetValueVolCol(paramNdx);
  5333. modified = true;
  5334. }
  5335. });
  5336. if(modified)
  5337. {
  5338. SetModified();
  5339. InvalidatePattern();
  5340. }
  5341. }
  5342. void CViewPattern::OnSelectPlugin(UINT nID)
  5343. {
  5344. CSoundFile *sndFile = GetSoundFile();
  5345. if(sndFile == nullptr)
  5346. return;
  5347. const CHANNELINDEX plugChannel = m_MenuCursor.GetChannel();
  5348. if(plugChannel < sndFile->GetNumChannels())
  5349. {
  5350. PLUGINDEX newPlug = static_cast<PLUGINDEX>(nID - ID_PLUGSELECT);
  5351. if(newPlug <= MAX_MIXPLUGINS && newPlug != sndFile->ChnSettings[plugChannel].nMixPlugin)
  5352. {
  5353. sndFile->ChnSettings[plugChannel].nMixPlugin = newPlug;
  5354. if(sndFile->GetModSpecifications().supportsPlugins)
  5355. {
  5356. SetModified(false);
  5357. }
  5358. InvalidateChannelsHeaders();
  5359. }
  5360. }
  5361. }
  5362. bool CViewPattern::HandleSplit(ModCommand &m, int note)
  5363. {
  5364. ModCommand::INSTR ins = static_cast<ModCommand::INSTR>(GetCurrentInstrument());
  5365. const bool isSplit = IsNoteSplit(note);
  5366. if(isSplit)
  5367. {
  5368. CModDoc *modDoc = GetDocument();
  5369. if(modDoc == nullptr)
  5370. return false;
  5371. const CSoundFile &sndFile = modDoc->GetSoundFile();
  5372. if(modDoc->GetSplitKeyboardSettings().octaveLink && note <= NOTE_MAX)
  5373. {
  5374. note += 12 * modDoc->GetSplitKeyboardSettings().octaveModifier;
  5375. Limit(note, sndFile.GetModSpecifications().noteMin, sndFile.GetModSpecifications().noteMax);
  5376. }
  5377. if(modDoc->GetSplitKeyboardSettings().splitInstrument)
  5378. {
  5379. ins = modDoc->GetSplitKeyboardSettings().splitInstrument;
  5380. }
  5381. }
  5382. m.note = static_cast<ModCommand::NOTE>(note);
  5383. if(ins)
  5384. {
  5385. m.instr = ins;
  5386. }
  5387. return isSplit;
  5388. }
  5389. bool CViewPattern::IsNoteSplit(int note) const
  5390. {
  5391. CModDoc *pModDoc = GetDocument();
  5392. return (pModDoc != nullptr
  5393. && pModDoc->GetSplitKeyboardSettings().IsSplitActive()
  5394. && note <= pModDoc->GetSplitKeyboardSettings().splitNote);
  5395. }
  5396. bool CViewPattern::BuildPluginCtxMenu(HMENU hMenu, UINT nChn, const CSoundFile &sndFile) const
  5397. {
  5398. for(PLUGINDEX plug = 0; plug <= MAX_MIXPLUGINS; plug++)
  5399. {
  5400. bool itemFound = false;
  5401. CString s;
  5402. if(!plug)
  5403. {
  5404. s = _T("No Plugin");
  5405. itemFound = true;
  5406. } else
  5407. {
  5408. const SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[plug - 1];
  5409. if(plugin.IsValidPlugin())
  5410. {
  5411. s.Format(_T("FX%u: "), plug);
  5412. s += mpt::ToCString(plugin.GetName());
  5413. itemFound = true;
  5414. }
  5415. }
  5416. if(itemFound)
  5417. {
  5418. UINT flags = MF_STRING | ((plug == sndFile.ChnSettings[nChn].nMixPlugin) ? MF_CHECKED : 0);
  5419. AppendMenu(hMenu, flags, ID_PLUGSELECT + plug, s);
  5420. }
  5421. }
  5422. return true;
  5423. }
  5424. bool CViewPattern::BuildSoloMuteCtxMenu(HMENU hMenu, CInputHandler *ih, UINT nChn, const CSoundFile &sndFile) const
  5425. {
  5426. AppendMenu(hMenu, sndFile.ChnSettings[nChn].dwFlags[CHN_MUTE] ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_PATTERN_MUTE, ih->GetKeyTextFromCommand(kcChannelMute, _T("&Mute Channel")));
  5427. bool solo = false, unmuteAll = false;
  5428. bool soloPending = false, unmuteAllPending = false; // doesn't work perfectly yet
  5429. for(CHANNELINDEX i = 0; i < sndFile.GetNumChannels(); i++)
  5430. {
  5431. if(i != nChn)
  5432. {
  5433. if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
  5434. solo = soloPending = true;
  5435. if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
  5436. soloPending = true;
  5437. } else
  5438. {
  5439. if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
  5440. solo = soloPending = true;
  5441. if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
  5442. soloPending = true;
  5443. }
  5444. if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
  5445. unmuteAll = unmuteAllPending = true;
  5446. if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
  5447. unmuteAllPending = true;
  5448. }
  5449. if(solo)
  5450. AppendMenu(hMenu, MF_STRING, ID_PATTERN_SOLO, ih->GetKeyTextFromCommand(kcChannelSolo, _T("&Solo Channel")));
  5451. if(unmuteAll)
  5452. AppendMenu(hMenu, MF_STRING, ID_PATTERN_UNMUTEALL, ih->GetKeyTextFromCommand(kcChannelUnmuteAll, _T("&Unmute All")));
  5453. AppendMenu(hMenu, sndFile.m_bChannelMuteTogglePending[nChn] ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_PATTERN_TRANSITIONMUTE, ih->GetKeyTextFromCommand(kcToggleChanMuteOnPatTransition, sndFile.ChnSettings[nChn].dwFlags[CHN_MUTE] ? _T("On Transition: Unmute\t") : _T("On Transition: Mute\t")));
  5454. if(unmuteAllPending)
  5455. AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSITION_UNMUTEALL, ih->GetKeyTextFromCommand(kcUnmuteAllChnOnPatTransition, _T("On Transition: Unmute All")));
  5456. if(soloPending)
  5457. AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSITIONSOLO, ih->GetKeyTextFromCommand(kcSoloChnOnPatTransition, _T("On Transition: Solo")));
  5458. AppendMenu(hMenu, MF_STRING, ID_PATTERN_CHNRESET, ih->GetKeyTextFromCommand(kcChannelReset, _T("&Reset Channel")));
  5459. return true;
  5460. }
  5461. bool CViewPattern::BuildRecordCtxMenu(HMENU hMenu, CInputHandler *ih, CHANNELINDEX nChn) const
  5462. {
  5463. const auto recordGroup = GetDocument()->GetChannelRecordGroup(nChn);
  5464. AppendMenu(hMenu, (recordGroup == RecordGroup::Group1) ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_EDIT_RECSELECT, ih->GetKeyTextFromCommand(kcChannelRecordSelect, _T("R&ecord Select")));
  5465. AppendMenu(hMenu, (recordGroup == RecordGroup::Group2) ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_EDIT_SPLITRECSELECT, ih->GetKeyTextFromCommand(kcChannelSplitRecordSelect, _T("S&plit Record Select")));
  5466. return true;
  5467. }
  5468. bool CViewPattern::BuildRowInsDelCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5469. {
  5470. HMENU subMenuInsert = CreatePopupMenu();
  5471. HMENU subMenuDelete = CreatePopupMenu();
  5472. const auto numRows = m_Selection.GetNumRows();
  5473. const CString label = (numRows != 1) ? MPT_CFORMAT("{} Rows")(numRows) : CString(_T("Row"));
  5474. AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTROW, ih->GetKeyTextFromCommand(kcInsertRow, _T("Insert ") + label + _T(" (&Selection)")));
  5475. AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTALLROW, ih->GetKeyTextFromCommand(kcInsertWholeRow, _T("Insert ") + label + _T(" (&All Channels)")));
  5476. AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTROWGLOBAL, ih->GetKeyTextFromCommand(kcInsertRowGlobal, _T("Insert ") + label + _T(" (Selection, &Global)")));
  5477. AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTALLROWGLOBAL, ih->GetKeyTextFromCommand(kcInsertWholeRowGlobal, _T("Insert ") + label + _T(" (All &Channels, Global)")));
  5478. AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(subMenuInsert), _T("&Insert ") + label);
  5479. AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEROW, ih->GetKeyTextFromCommand(kcDeleteRow, _T("Delete ") + label + _T(" (&Selection)")));
  5480. AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEALLROW, ih->GetKeyTextFromCommand(kcDeleteWholeRow, _T("Delete ") + label + _T(" (&All Channels)")));
  5481. AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEROWGLOBAL, ih->GetKeyTextFromCommand(kcDeleteRowGlobal, _T("Delete ") + label + _T(" (Selection, &Global)")));
  5482. AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEALLROWGLOBAL, ih->GetKeyTextFromCommand(kcDeleteWholeRowGlobal, _T("Delete ") + label + _T(" (All &Channels, Global)")));
  5483. AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(subMenuDelete), _T("&Delete ") + label);
  5484. return true;
  5485. }
  5486. bool CViewPattern::BuildMiscCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5487. {
  5488. AppendMenu(hMenu, MF_STRING, ID_SHOWTIMEATROW, ih->GetKeyTextFromCommand(kcTimeAtRow, _T("Show Row Play Time")));
  5489. if(m_Selection.GetStartRow() == m_Selection.GetEndRow())
  5490. {
  5491. CString s;
  5492. s.Format(_T("Split Pattern at Ro&w %u"), m_Selection.GetStartRow());
  5493. AppendMenu(hMenu, MF_STRING | (m_Selection.GetStartRow() < 1 ? MF_GRAYED : 0), ID_PATTERN_SPLIT, ih->GetKeyTextFromCommand(kcSplitPattern, s));
  5494. }
  5495. const CSoundFile &sndFile = *GetSoundFile();
  5496. CString lockStr;
  5497. bool lockActive = (sndFile.m_lockRowStart != ROWINDEX_INVALID);
  5498. if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
  5499. {
  5500. lockStr = _T("&Lock Playback to Selection");
  5501. if(lockActive)
  5502. {
  5503. lockStr.AppendFormat(_T(" (Current: %u-%u)"), sndFile.m_lockRowStart, sndFile.m_lockRowEnd);
  5504. }
  5505. } else if(lockActive)
  5506. {
  5507. lockStr = _T("Reset Playback &Lock");
  5508. } else
  5509. {
  5510. return true;
  5511. }
  5512. AppendMenu(hMenu, MF_STRING | (lockActive ? MF_CHECKED : 0), ID_LOCK_PATTERN_ROWS, ih->GetKeyTextFromCommand(kcLockPlaybackToRows, lockStr));
  5513. return true;
  5514. }
  5515. bool CViewPattern::BuildSelectionCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5516. {
  5517. AppendMenu(hMenu, MF_STRING, ID_EDIT_SELECTCOLUMN, ih->GetKeyTextFromCommand(kcSelectChannel, _T("Select &Channel")));
  5518. AppendMenu(hMenu, MF_STRING, ID_EDIT_SELECT_ALL, ih->GetKeyTextFromCommand(kcEditSelectAll, _T("Select &Pattern")));
  5519. return true;
  5520. }
  5521. bool CViewPattern::BuildGrowShrinkCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5522. {
  5523. AppendMenu(hMenu, MF_STRING, ID_GROW_SELECTION, ih->GetKeyTextFromCommand(kcPatternGrowSelection, _T("&Grow selection")));
  5524. AppendMenu(hMenu, MF_STRING, ID_SHRINK_SELECTION, ih->GetKeyTextFromCommand(kcPatternShrinkSelection, _T("&Shrink selection")));
  5525. return true;
  5526. }
  5527. bool CViewPattern::BuildInterpolationCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5528. {
  5529. const CSoundFile *sndFile = GetSoundFile();
  5530. const bool isPCNote = sndFile->Patterns.IsValidPat(m_nPattern) && sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel())->IsPcNote();
  5531. HMENU subMenu = CreatePopupMenu();
  5532. bool possible = BuildInterpolationCtxMenu(subMenu, PatternCursor::noteColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateNote, _T("&Note Column")), ID_PATTERN_INTERPOLATE_NOTE)
  5533. | BuildInterpolationCtxMenu(subMenu, PatternCursor::instrColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateInstr, isPCNote ? _T("&Plugin Column") : _T("&Instrument Column")), ID_PATTERN_INTERPOLATE_INSTR)
  5534. | BuildInterpolationCtxMenu(subMenu, PatternCursor::volumeColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateVol, isPCNote ? _T("&Parameter Column") : _T("&Volume Column")), ID_PATTERN_INTERPOLATE_VOLUME)
  5535. | BuildInterpolationCtxMenu(subMenu, PatternCursor::effectColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateEffect, isPCNote ? _T("&Value Column") : _T("&Effect Column")), ID_PATTERN_INTERPOLATE_EFFECT);
  5536. if(possible || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5537. {
  5538. AppendMenu(hMenu, MF_POPUP | (possible ? 0 : MF_GRAYED), reinterpret_cast<UINT_PTR>(subMenu), _T("I&nterpolate..."));
  5539. return true;
  5540. }
  5541. return false;
  5542. }
  5543. bool CViewPattern::BuildInterpolationCtxMenu(HMENU hMenu, PatternCursor::Columns colType, CString label, UINT command) const
  5544. {
  5545. bool possible = IsInterpolationPossible(colType);
  5546. if(!possible && colType == PatternCursor::effectColumn)
  5547. {
  5548. // Extend search to param column
  5549. possible = IsInterpolationPossible(PatternCursor::paramColumn);
  5550. }
  5551. if(possible || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5552. {
  5553. AppendMenu(hMenu, MF_STRING | (possible ? 0 : MF_GRAYED), command, label);
  5554. }
  5555. return possible;
  5556. }
  5557. bool CViewPattern::BuildEditCtxMenu(HMENU hMenu, CInputHandler *ih, CModDoc *pModDoc) const
  5558. {
  5559. HMENU pasteSpecialMenu = ::CreatePopupMenu();
  5560. AppendMenu(hMenu, MF_STRING, ID_EDIT_CUT, ih->GetKeyTextFromCommand(kcEditCut, _T("Cu&t")));
  5561. AppendMenu(hMenu, MF_STRING, ID_EDIT_COPY, ih->GetKeyTextFromCommand(kcEditCopy, _T("&Copy")));
  5562. AppendMenu(hMenu, MF_STRING | (PatternClipboard::CanPaste() ? 0 : MF_GRAYED), ID_EDIT_PASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("&Paste")));
  5563. AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(pasteSpecialMenu), _T("Paste Special"));
  5564. AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_MIXPASTE, ih->GetKeyTextFromCommand(kcEditMixPaste, _T("&Mix Paste")));
  5565. AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_MIXPASTE_ITSTYLE, ih->GetKeyTextFromCommand(kcEditMixPasteITStyle, _T("M&ix Paste (IT Style)")));
  5566. AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_PASTEFLOOD, ih->GetKeyTextFromCommand(kcEditPasteFlood, _T("Paste Fl&ood")));
  5567. AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_PUSHFORWARDPASTE, ih->GetKeyTextFromCommand(kcEditPushForwardPaste, _T("&Push Forward Paste (Insert)")));
  5568. DWORD greyed = pModDoc->GetPatternUndo().CanUndo() ? MF_ENABLED : MF_GRAYED;
  5569. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5570. {
  5571. AppendMenu(hMenu, MF_STRING | greyed, ID_EDIT_UNDO, ih->GetKeyTextFromCommand(kcEditUndo, _T("&Undo")));
  5572. }
  5573. greyed = pModDoc->GetPatternUndo().CanRedo() ? MF_ENABLED : MF_GRAYED;
  5574. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5575. {
  5576. AppendMenu(hMenu, MF_STRING | greyed, ID_EDIT_REDO, ih->GetKeyTextFromCommand(kcEditRedo, _T("&Redo")));
  5577. }
  5578. AppendMenu(hMenu, MF_STRING, ID_CLEAR_SELECTION, ih->GetKeyTextFromCommand(kcSampleDelete, _T("Clear Selection")));
  5579. return true;
  5580. }
  5581. bool CViewPattern::BuildVisFXCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5582. {
  5583. DWORD greyed = (IsColumnSelected(PatternCursor::effectColumn) || IsColumnSelected(PatternCursor::paramColumn)) ? FALSE : MF_GRAYED;
  5584. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5585. {
  5586. AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_VISUALIZE_EFFECT, ih->GetKeyTextFromCommand(kcPatternVisualizeEffect, _T("&Visualize Effect")));
  5587. return true;
  5588. }
  5589. return false;
  5590. }
  5591. bool CViewPattern::BuildTransposeCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5592. {
  5593. HMENU transMenu = CreatePopupMenu();
  5594. std::vector<CHANNELINDEX> validChans;
  5595. DWORD greyed = IsColumnSelected(PatternCursor::noteColumn) ? FALSE : MF_GRAYED;
  5596. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5597. {
  5598. AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_UP, ih->GetKeyTextFromCommand(kcTransposeUp, _T("Transpose +&1")));
  5599. AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_DOWN, ih->GetKeyTextFromCommand(kcTransposeDown, _T("Transpose -&1")));
  5600. AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_OCTUP, ih->GetKeyTextFromCommand(kcTransposeOctUp, _T("Transpose +1&2")));
  5601. AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_OCTDOWN, ih->GetKeyTextFromCommand(kcTransposeOctDown, _T("Transpose -1&2")));
  5602. AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_CUSTOM, ih->GetKeyTextFromCommand(kcTransposeCustom, _T("&Custom...")));
  5603. AppendMenu(hMenu, MF_POPUP | greyed, reinterpret_cast<UINT_PTR>(transMenu), _T("&Transpose..."));
  5604. return true;
  5605. }
  5606. return false;
  5607. }
  5608. bool CViewPattern::BuildAmplifyCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5609. {
  5610. std::vector<CHANNELINDEX> validChans;
  5611. DWORD greyed = IsColumnSelected(PatternCursor::volumeColumn) ? 0 : MF_GRAYED;
  5612. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5613. {
  5614. AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_AMPLIFY, ih->GetKeyTextFromCommand(kcPatternAmplify, _T("&Amplify")));
  5615. return true;
  5616. }
  5617. return false;
  5618. }
  5619. bool CViewPattern::BuildChannelControlCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5620. {
  5621. const CModSpecifications &specs = GetDocument()->GetSoundFile().GetModSpecifications();
  5622. CHANNELINDEX numChannels = GetDocument()->GetNumChannels();
  5623. DWORD canAddChannels = (numChannels < specs.channelsMax) ? 0 : MF_GRAYED;
  5624. DWORD canRemoveChannels = (numChannels > specs.channelsMin) ? 0 : MF_GRAYED;
  5625. AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
  5626. AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSPOSECHANNEL, ih->GetKeyTextFromCommand(kcChannelTranspose, _T("&Transpose Channel")));
  5627. AppendMenu(hMenu, MF_STRING | canAddChannels, ID_PATTERN_DUPLICATECHANNEL, ih->GetKeyTextFromCommand(kcChannelDuplicate, _T("&Duplicate Channel")));
  5628. HMENU addChannelMenu = ::CreatePopupMenu();
  5629. AppendMenu(hMenu, MF_POPUP | canAddChannels, reinterpret_cast<UINT_PTR>(addChannelMenu), _T("&Add Channel\t"));
  5630. AppendMenu(addChannelMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_FRONT, ih->GetKeyTextFromCommand(kcChannelAddBefore, _T("&Before this channel")));
  5631. AppendMenu(addChannelMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_AFTER, ih->GetKeyTextFromCommand(kcChannelAddAfter, _T("&After this channel")));
  5632. HMENU removeChannelMenu = ::CreatePopupMenu();
  5633. AppendMenu(hMenu, MF_POPUP | canRemoveChannels, reinterpret_cast<UINT_PTR>(removeChannelMenu), _T("Remo&ve Channel\t"));
  5634. AppendMenu(removeChannelMenu, MF_STRING, ID_PATTERN_REMOVECHANNEL, ih->GetKeyTextFromCommand(kcChannelRemove, _T("&Remove this channel\t")));
  5635. AppendMenu(removeChannelMenu, MF_STRING, ID_PATTERN_REMOVECHANNELDIALOG, _T("&Choose channels to remove...\t"));
  5636. AppendMenu(hMenu, MF_STRING, ID_PATTERN_RESETCHANNELCOLORS, _T("Reset Channel &Colours"));
  5637. return false;
  5638. }
  5639. bool CViewPattern::BuildSetInstCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5640. {
  5641. const CSoundFile *sndFile = GetSoundFile();
  5642. const CModDoc *modDoc;
  5643. if(sndFile == nullptr || (modDoc = sndFile->GetpModDoc()) == nullptr)
  5644. {
  5645. return false;
  5646. }
  5647. std::vector<CHANNELINDEX> validChans;
  5648. DWORD greyed = IsColumnSelected(PatternCursor::instrColumn) ? 0 : MF_GRAYED;
  5649. if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
  5650. {
  5651. if((sndFile->Patterns.IsValidPat(m_nPattern)))
  5652. {
  5653. if(sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel())->IsPcNote())
  5654. {
  5655. // Don't build instrument menu for PC notes.
  5656. return false;
  5657. }
  5658. }
  5659. // Create the new menu and add it to the existing menu.
  5660. HMENU instrumentChangeMenu = ::CreatePopupMenu();
  5661. AppendMenu(hMenu, MF_POPUP | greyed, reinterpret_cast<UINT_PTR>(instrumentChangeMenu), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("Change Instrument")));
  5662. if(!greyed)
  5663. {
  5664. bool addSeparator = false;
  5665. if(sndFile->GetNumInstruments())
  5666. {
  5667. for(INSTRUMENTINDEX i = 1; i <= sndFile->GetNumInstruments(); i++)
  5668. {
  5669. if(sndFile->Instruments[i] == nullptr)
  5670. continue;
  5671. CString instString = modDoc->GetPatternViewInstrumentName(i, true);
  5672. if(!instString.IsEmpty())
  5673. {
  5674. AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + i, modDoc->GetPatternViewInstrumentName(i));
  5675. addSeparator = true;
  5676. }
  5677. }
  5678. } else
  5679. {
  5680. CString s;
  5681. for(SAMPLEINDEX i = 1; i <= sndFile->GetNumSamples(); i++) if (sndFile->GetSample(i).HasSampleData())
  5682. {
  5683. s.Format(_T("%02d: "), i);
  5684. s += mpt::ToCString(sndFile->GetCharsetInternal(), sndFile->GetSampleName(i));
  5685. AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + i, s);
  5686. addSeparator = true;
  5687. }
  5688. }
  5689. // Add options to remove instrument from selection.
  5690. if(addSeparator)
  5691. {
  5692. AppendMenu(instrumentChangeMenu, MF_SEPARATOR, 0, 0);
  5693. }
  5694. AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT, _T("&Remove Instrument"));
  5695. AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + GetCurrentInstrument(), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("&Current Instrument")));
  5696. AppendMenu(instrumentChangeMenu, MF_STRING, ID_PATTERN_SETINSTRUMENT, ih->GetKeyTextFromCommand(kcPatternSetInstrumentNotEmpty, _T("Current Instrument (&only change existing)")));
  5697. }
  5698. return BuildTogglePlugEditorCtxMenu(hMenu, ih);
  5699. }
  5700. return false;
  5701. }
  5702. // Context menu for Param Control notes
  5703. bool CViewPattern::BuildPCNoteCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5704. {
  5705. const CSoundFile *sndFile = GetSoundFile();
  5706. if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
  5707. {
  5708. return false;
  5709. }
  5710. const ModCommand &selStart = *sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
  5711. if(!selStart.IsPcNote())
  5712. {
  5713. return false;
  5714. }
  5715. CString s;
  5716. // Create sub menu for "change plugin"
  5717. HMENU pluginChangeMenu = ::CreatePopupMenu();
  5718. AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(pluginChangeMenu), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("Change Plugin")));
  5719. for(PLUGINDEX nPlg = 0; nPlg < MAX_MIXPLUGINS; nPlg++)
  5720. {
  5721. if(sndFile->m_MixPlugins[nPlg].pMixPlugin != nullptr)
  5722. {
  5723. s = MPT_CFORMAT("{}: {}")(mpt::cfmt::dec0<2>(nPlg + 1), mpt::ToCString(sndFile->m_MixPlugins[nPlg].GetName()));
  5724. AppendMenu(pluginChangeMenu, MF_STRING | (((nPlg + 1) == selStart.instr) ? MF_CHECKED : 0), ID_CHANGE_INSTRUMENT + nPlg + 1, s);
  5725. }
  5726. }
  5727. if(selStart.instr >= 1 && selStart.instr <= MAX_MIXPLUGINS)
  5728. {
  5729. const SNDMIXPLUGIN &plug = sndFile->m_MixPlugins[selStart.instr - 1];
  5730. if(plug.pMixPlugin != nullptr)
  5731. {
  5732. // Create sub menu for "change plugin param"
  5733. HMENU paramChangeMenu = ::CreatePopupMenu();
  5734. AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(paramChangeMenu), _T("Change Plugin Parameter\t"));
  5735. const PlugParamIndex curParam = selStart.GetValueVolCol(), nParams = plug.pMixPlugin->GetNumParameters();
  5736. for(PlugParamIndex i = 0; i < nParams; i++)
  5737. {
  5738. AppendMenu(paramChangeMenu, MF_STRING | ((i == curParam) ? MF_CHECKED : 0), ID_CHANGE_PCNOTE_PARAM + i, plug.pMixPlugin->GetFormattedParamName(i));
  5739. }
  5740. }
  5741. }
  5742. return BuildTogglePlugEditorCtxMenu(hMenu, ih);
  5743. }
  5744. bool CViewPattern::BuildTogglePlugEditorCtxMenu(HMENU hMenu, CInputHandler *ih) const
  5745. {
  5746. const CSoundFile *sndFile = GetSoundFile();
  5747. if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
  5748. {
  5749. return false;
  5750. }
  5751. PLUGINDEX plug = 0;
  5752. const ModCommand &selStart = *sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
  5753. if(selStart.IsPcNote())
  5754. {
  5755. // PC Event
  5756. plug = selStart.instr;
  5757. } else if(selStart.instr > 0 && selStart.instr <= sndFile->GetNumInstruments()
  5758. && sndFile->Instruments[selStart.instr] != nullptr
  5759. && sndFile->Instruments[selStart.instr]->nMixPlug)
  5760. {
  5761. // Regular instrument
  5762. plug = sndFile->Instruments[selStart.instr]->nMixPlug;
  5763. }
  5764. if(plug && plug <= MAX_MIXPLUGINS && sndFile->m_MixPlugins[plug - 1].pMixPlugin != nullptr)
  5765. {
  5766. AppendMenu(hMenu, MF_STRING, ID_PATTERN_EDIT_PCNOTE_PLUGIN, ih->GetKeyTextFromCommand(kcPatternEditPCNotePlugin, _T("Toggle Plugin &Editor")));
  5767. return true;
  5768. }
  5769. return false;
  5770. }
  5771. // Returns an ordered list of all channels in which a given column type is selected.
  5772. CHANNELINDEX CViewPattern::ListChansWhereColSelected(PatternCursor::Columns colType, std::vector<CHANNELINDEX> &chans) const
  5773. {
  5774. CHANNELINDEX startChan = m_Selection.GetStartChannel();
  5775. CHANNELINDEX endChan = m_Selection.GetEndChannel();
  5776. chans.clear();
  5777. chans.reserve(endChan - startChan + 1);
  5778. // Check in which channels this column is selected.
  5779. // Actually this check is only important for the first and last channel, but to keep things clean and simple, all channels are checked in the same manner.
  5780. for(CHANNELINDEX i = startChan; i <= endChan; i++)
  5781. {
  5782. if(m_Selection.ContainsHorizontal(PatternCursor(0, i, colType)))
  5783. {
  5784. chans.push_back(i);
  5785. }
  5786. }
  5787. return static_cast<CHANNELINDEX>(chans.size());
  5788. }
  5789. // Check if a column type is selected on any channel in the current selection.
  5790. bool CViewPattern::IsColumnSelected(PatternCursor::Columns colType) const
  5791. {
  5792. return m_Selection.ContainsHorizontal(PatternCursor(0, m_Selection.GetStartChannel(), colType))
  5793. || m_Selection.ContainsHorizontal(PatternCursor(0, m_Selection.GetEndChannel(), colType));
  5794. }
  5795. // Check if the given interpolation type is actually possible in the current selection.
  5796. bool CViewPattern::IsInterpolationPossible(PatternCursor::Columns colType) const
  5797. {
  5798. std::vector<CHANNELINDEX> validChans;
  5799. ListChansWhereColSelected(colType, validChans);
  5800. ROWINDEX startRow = m_Selection.GetStartRow();
  5801. ROWINDEX endRow = m_Selection.GetEndRow();
  5802. for(auto chn : validChans)
  5803. {
  5804. if(IsInterpolationPossible(startRow, endRow, chn, colType))
  5805. {
  5806. return true;
  5807. }
  5808. }
  5809. return false;
  5810. }
  5811. // Check if the given interpolation type is actually possible in a given channel.
  5812. bool CViewPattern::IsInterpolationPossible(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX chan, PatternCursor::Columns colType) const
  5813. {
  5814. const CSoundFile *sndFile = GetSoundFile();
  5815. if(startRow == endRow || sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
  5816. return false;
  5817. bool result = false;
  5818. const ModCommand &startRowMC = *sndFile->Patterns[m_nPattern].GetpModCommand(startRow, chan);
  5819. const ModCommand &endRowMC = *sndFile->Patterns[m_nPattern].GetpModCommand(endRow, chan);
  5820. UINT startRowCmd, endRowCmd;
  5821. if(colType == PatternCursor::effectColumn && (startRowMC.IsPcNote() || endRowMC.IsPcNote()))
  5822. return true;
  5823. switch(colType)
  5824. {
  5825. case PatternCursor::noteColumn:
  5826. startRowCmd = startRowMC.note;
  5827. endRowCmd = endRowMC.note;
  5828. result = (startRowCmd == endRowCmd && startRowCmd != NOTE_NONE) // Interpolate between two identical notes or Cut / Fade / etc...
  5829. || (startRowCmd != NOTE_NONE && endRowCmd == NOTE_NONE) // Fill in values from the first row
  5830. || (startRowCmd == NOTE_NONE && endRowCmd != NOTE_NONE) // Fill in values from the last row
  5831. || (ModCommand::IsNoteOrEmpty(startRowMC.note) && ModCommand::IsNoteOrEmpty(endRowMC.note) && !(startRowCmd == NOTE_NONE && endRowCmd == NOTE_NONE)); // Interpolate between two notes of which one may be empty
  5832. break;
  5833. case PatternCursor::instrColumn:
  5834. startRowCmd = startRowMC.instr;
  5835. endRowCmd = endRowMC.instr;
  5836. result = startRowCmd != 0 || endRowCmd != 0;
  5837. break;
  5838. case PatternCursor::volumeColumn:
  5839. startRowCmd = startRowMC.volcmd;
  5840. endRowCmd = endRowMC.volcmd;
  5841. result = (startRowCmd == endRowCmd && startRowCmd != VOLCMD_NONE) // Interpolate between two identical commands
  5842. || (startRowCmd != VOLCMD_NONE && endRowCmd == VOLCMD_NONE) // Fill in values from the first row
  5843. || (startRowCmd == VOLCMD_NONE && endRowCmd != VOLCMD_NONE); // Fill in values from the last row
  5844. break;
  5845. case PatternCursor::effectColumn:
  5846. case PatternCursor::paramColumn:
  5847. startRowCmd = startRowMC.command;
  5848. endRowCmd = endRowMC.command;
  5849. result = (startRowCmd == endRowCmd && startRowCmd != CMD_NONE) // Interpolate between two identical commands
  5850. || (startRowCmd != CMD_NONE && endRowCmd == CMD_NONE) // Fill in values from the first row
  5851. || (startRowCmd == CMD_NONE && endRowCmd != CMD_NONE); // Fill in values from the last row
  5852. break;
  5853. default:
  5854. result = false;
  5855. }
  5856. return result;
  5857. }
  5858. void CViewPattern::OnRButtonDblClk(UINT nFlags, CPoint point)
  5859. {
  5860. OnRButtonDown(nFlags, point);
  5861. CModScrollView::OnRButtonDblClk(nFlags, point);
  5862. }
  5863. // Toggle pending mute status for channel from context menu.
  5864. void CViewPattern::OnTogglePendingMuteFromClick()
  5865. {
  5866. TogglePendingMute(m_MenuCursor.GetChannel());
  5867. }
  5868. // Toggle pending solo status for channel from context menu.
  5869. void CViewPattern::OnPendingSoloChnFromClick()
  5870. {
  5871. PendingSoloChn(m_MenuCursor.GetChannel());
  5872. }
  5873. // Set pending unmute status for all channels.
  5874. void CViewPattern::OnPendingUnmuteAllChnFromClick()
  5875. {
  5876. CSoundFile *pSndFile = GetSoundFile();
  5877. if(pSndFile != nullptr)
  5878. {
  5879. GetSoundFile()->PatternTransitionChnUnmuteAll();
  5880. InvalidateChannelsHeaders();
  5881. }
  5882. }
  5883. // Toggle pending solo status for a channel.
  5884. void CViewPattern::PendingSoloChn(CHANNELINDEX nChn)
  5885. {
  5886. CSoundFile *pSndFile = GetSoundFile();
  5887. if(pSndFile != nullptr)
  5888. {
  5889. GetSoundFile()->PatternTranstionChnSolo(nChn);
  5890. InvalidateChannelsHeaders();
  5891. }
  5892. }
  5893. // Toggle pending mute status for a channel.
  5894. void CViewPattern::TogglePendingMute(CHANNELINDEX nChn)
  5895. {
  5896. CSoundFile *pSndFile = GetSoundFile();
  5897. if(pSndFile != nullptr)
  5898. {
  5899. pSndFile->m_bChannelMuteTogglePending[nChn] = !pSndFile->m_bChannelMuteTogglePending[nChn];
  5900. InvalidateChannelsHeaders();
  5901. }
  5902. }
  5903. // Check if editing is enabled, and if it's not, prompt the user to enable editing.
  5904. bool CViewPattern::IsEditingEnabled_bmsg()
  5905. {
  5906. if(IsEditingEnabled())
  5907. return true;
  5908. if(TrackerSettings::Instance().patternNoEditPopup)
  5909. return false;
  5910. HMENU hMenu;
  5911. if((hMenu = ::CreatePopupMenu()) == nullptr)
  5912. return false;
  5913. CPoint pt = GetPointFromPosition(m_Cursor);
  5914. // We add an mnemonic for an unbreakable space to avoid activating edit mode accidentally.
  5915. AppendMenuW(hMenu, MF_STRING, IDC_PATTERN_RECORD, L"Editing (recording) is disabled;&\u00A0 click here to enable it.");
  5916. ClientToScreen(&pt);
  5917. ::TrackPopupMenu(hMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hWnd, NULL);
  5918. ::DestroyMenu(hMenu);
  5919. return false;
  5920. }
  5921. // Show playback time at a given pattern position.
  5922. void CViewPattern::OnShowTimeAtRow()
  5923. {
  5924. CSoundFile *pSndFile = GetSoundFile();
  5925. if(pSndFile == nullptr)
  5926. {
  5927. return;
  5928. }
  5929. CString msg;
  5930. const auto &order = Order();
  5931. ORDERINDEX currentOrder = GetCurrentOrder();
  5932. if(currentOrder < order.size() && order[currentOrder] == m_nPattern)
  5933. {
  5934. const double t = pSndFile->GetPlaybackTimeAt(currentOrder, GetCurrentRow(), false, false);
  5935. if(t < 0)
  5936. msg.Format(_T("Unable to determine the time. Possible cause: No order %d, row %u found in play sequence."), currentOrder, GetCurrentRow());
  5937. else
  5938. {
  5939. const uint32 minutes = static_cast<uint32>(t / 60.0);
  5940. const double seconds = t - (minutes * 60);
  5941. msg.Format(_T("Estimate for playback time at order %d (pattern %d), row %u: %u minute%s %.2f seconds."), currentOrder, m_nPattern, GetCurrentRow(), minutes, (minutes == 1) ? _T("") : _T("s"), seconds);
  5942. }
  5943. } else
  5944. {
  5945. msg.Format(_T("Unable to determine the time: pattern at current order (%d) does not correspond to pattern in pattern view (pattern %d)."), currentOrder, m_nPattern);
  5946. }
  5947. Reporting::Notification(msg);
  5948. }
  5949. // Set up split keyboard
  5950. void CViewPattern::SetSplitKeyboardSettings()
  5951. {
  5952. CModDoc *pModDoc = GetDocument();
  5953. if(pModDoc == nullptr)
  5954. return;
  5955. CSplitKeyboardSettings dlg(CMainFrame::GetMainFrame(), pModDoc->GetSoundFile(), pModDoc->GetSplitKeyboardSettings());
  5956. if(dlg.DoModal() == IDOK)
  5957. {
  5958. // Update split keyboard settings in other pattern views
  5959. pModDoc->UpdateAllViews(NULL, SampleHint().Names());
  5960. }
  5961. }
  5962. // Paste pattern data using the given paste mode.
  5963. void CViewPattern::ExecutePaste(PatternClipboard::PasteModes mode)
  5964. {
  5965. if(IsEditingEnabled_bmsg() && PastePattern(m_nPattern, m_Selection.GetUpperLeft(), mode))
  5966. {
  5967. InvalidatePattern(false);
  5968. SetFocus();
  5969. }
  5970. }
  5971. // Show plugin editor for plugin assigned to PC Event at the cursor position.
  5972. void CViewPattern::OnTogglePCNotePluginEditor()
  5973. {
  5974. CModDoc *pModDoc = GetDocument();
  5975. if(pModDoc == nullptr)
  5976. return;
  5977. CSoundFile &sndFile = pModDoc->GetSoundFile();
  5978. if(!sndFile.Patterns.IsValidPat(m_nPattern))
  5979. return;
  5980. const ModCommand &m = *sndFile.Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
  5981. PLUGINDEX plug = 0;
  5982. if(!m.IsPcNote())
  5983. {
  5984. // No PC note: Toggle instrument's plugin editor
  5985. if(m.instr && m.instr <= sndFile.GetNumInstruments() && sndFile.Instruments[m.instr])
  5986. {
  5987. plug = sndFile.Instruments[m.instr]->nMixPlug;
  5988. }
  5989. } else
  5990. {
  5991. plug = m.instr;
  5992. }
  5993. if(plug > 0 && plug <= MAX_MIXPLUGINS)
  5994. pModDoc->TogglePluginEditor(plug - 1);
  5995. }
  5996. // Get the active pattern's rows per beat, or, if they are not overriden, the song's default rows per beat.
  5997. ROWINDEX CViewPattern::GetRowsPerBeat() const
  5998. {
  5999. const CSoundFile *pSndFile = GetSoundFile();
  6000. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  6001. return 0;
  6002. if(!pSndFile->Patterns[m_nPattern].GetOverrideSignature())
  6003. return pSndFile->m_nDefaultRowsPerBeat;
  6004. else
  6005. return pSndFile->Patterns[m_nPattern].GetRowsPerBeat();
  6006. }
  6007. // Get the active pattern's rows per measure, or, if they are not overriden, the song's default rows per measure.
  6008. ROWINDEX CViewPattern::GetRowsPerMeasure() const
  6009. {
  6010. const CSoundFile *pSndFile = GetSoundFile();
  6011. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  6012. return 0;
  6013. if(!pSndFile->Patterns[m_nPattern].GetOverrideSignature())
  6014. return pSndFile->m_nDefaultRowsPerMeasure;
  6015. else
  6016. return pSndFile->Patterns[m_nPattern].GetRowsPerMeasure();
  6017. }
  6018. // Set instrument
  6019. void CViewPattern::SetSelectionInstrument(const INSTRUMENTINDEX instr, bool setEmptyInstrument)
  6020. {
  6021. CSoundFile *pSndFile = GetSoundFile();
  6022. if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
  6023. {
  6024. return;
  6025. }
  6026. BeginWaitCursor();
  6027. PrepareUndo(m_Selection, "Set Instrument");
  6028. bool modified = false;
  6029. ApplyToSelection([instr, setEmptyInstrument, &modified] (ModCommand &m, ROWINDEX, CHANNELINDEX)
  6030. {
  6031. // If a note or an instr is present on the row, do the change, if required.
  6032. // Do not set instr if note and instr are both blank,
  6033. // but set instr if note is a PC note and instr is blank.
  6034. if(((setEmptyInstrument && (m.IsNote() || m.IsPcNote())) || m.instr != 0)
  6035. && (m.instr != instr))
  6036. {
  6037. m.instr = static_cast<ModCommand::INSTR>(instr);
  6038. modified = true;
  6039. }
  6040. });
  6041. if(modified)
  6042. {
  6043. SetModified();
  6044. InvalidatePattern();
  6045. }
  6046. EndWaitCursor();
  6047. }
  6048. // Select a whole beat (selectBeat = true) or measure.
  6049. void CViewPattern::SelectBeatOrMeasure(bool selectBeat)
  6050. {
  6051. const ROWINDEX adjust = selectBeat ? GetRowsPerBeat() : GetRowsPerMeasure();
  6052. // Snap to start of beat / measure of upper-left corner of current selection
  6053. const ROWINDEX startRow = m_Selection.GetStartRow() - (m_Selection.GetStartRow() % adjust);
  6054. // Snap to end of beat / measure of lower-right corner of current selection
  6055. const ROWINDEX endRow = m_Selection.GetEndRow() + adjust - (m_Selection.GetEndRow() % adjust) - 1;
  6056. CHANNELINDEX startChannel = m_Selection.GetStartChannel(), endChannel = m_Selection.GetEndChannel();
  6057. PatternCursor::Columns startColumn = PatternCursor::firstColumn, endColumn = PatternCursor::firstColumn;
  6058. if(m_Selection.GetUpperLeft() == m_Selection.GetLowerRight())
  6059. {
  6060. // No selection has been made yet => expand selection to whole channel.
  6061. endColumn = PatternCursor::lastColumn; // Extend to param column
  6062. } else if(startRow == m_Selection.GetStartRow() && endRow == m_Selection.GetEndRow())
  6063. {
  6064. // Whole beat or measure is already selected
  6065. if(m_Selection.GetStartColumn() == PatternCursor::firstColumn && m_Selection.GetEndColumn() == PatternCursor::lastColumn)
  6066. {
  6067. // Whole channel is already selected => expand selection to whole row.
  6068. startChannel = 0;
  6069. startColumn = PatternCursor::firstColumn;
  6070. endChannel = MAX_BASECHANNELS;
  6071. endColumn = PatternCursor::lastColumn;
  6072. } else
  6073. {
  6074. // Channel is only partly selected => expand to whole channel first.
  6075. endColumn = PatternCursor::lastColumn; // Extend to param column
  6076. }
  6077. } else
  6078. {
  6079. // Some arbitrary selection: Remember start / end column
  6080. startColumn = m_Selection.GetStartColumn();
  6081. endColumn = m_Selection.GetEndColumn();
  6082. }
  6083. SetCurSel(PatternCursor(startRow, startChannel, startColumn), PatternCursor(endRow, endChannel, endColumn));
  6084. }
  6085. // Sweep pattern channel to find instrument number to use
  6086. void CViewPattern::FindInstrument()
  6087. {
  6088. const CSoundFile *sndFile = GetSoundFile();
  6089. if(sndFile == nullptr)
  6090. {
  6091. return;
  6092. }
  6093. const auto &order = Order();
  6094. ORDERINDEX ord = GetCurrentOrder();
  6095. PATTERNINDEX pat = m_nPattern;
  6096. ROWINDEX row = m_Cursor.GetRow();
  6097. while(sndFile->Patterns.IsValidPat(pat))
  6098. {
  6099. // Seek upwards
  6100. do
  6101. {
  6102. auto &m = *sndFile->Patterns[pat].GetpModCommand(row, m_Cursor.GetChannel());
  6103. if(!m.IsPcNote() && m.instr != 0)
  6104. {
  6105. SendCtrlMessage(CTRLMSG_SETCURRENTINSTRUMENT, m.instr);
  6106. static_cast<CModControlView *>(CWnd::FromHandle(m_hWndCtrl))->InstrumentChanged(m.instr);
  6107. return;
  6108. }
  6109. } while(row-- != 0);
  6110. // Try previous pattern
  6111. if(ord == 0)
  6112. {
  6113. return;
  6114. }
  6115. ord = order.GetPreviousOrderIgnoringSkips(ord);
  6116. pat = order[ord];
  6117. if(!sndFile->Patterns.IsValidPat(pat))
  6118. {
  6119. return;
  6120. }
  6121. row = sndFile->Patterns[pat].GetNumRows() - 1;
  6122. }
  6123. }
  6124. // Find previous or next column entry (note, instrument, ...) on this channel
  6125. void CViewPattern::JumpToPrevOrNextEntry(bool nextEntry, bool select)
  6126. {
  6127. const CSoundFile *sndFile = GetSoundFile();
  6128. if(sndFile == nullptr || GetCurrentOrder() >= Order().size())
  6129. {
  6130. return;
  6131. }
  6132. const auto &order = Order();
  6133. ORDERINDEX ord = GetCurrentOrder();
  6134. PATTERNINDEX pat = m_nPattern;
  6135. CHANNELINDEX chn = m_Cursor.GetChannel();
  6136. PatternCursor::Columns column = m_Cursor.GetColumnType();
  6137. int32 row = m_Cursor.GetRow();
  6138. int direction = nextEntry ? 1 : -1;
  6139. row += direction; // Don't want to find the cell we're already in
  6140. while(sndFile->Patterns.IsValidPat(pat))
  6141. {
  6142. while(sndFile->Patterns[pat].IsValidRow(row))
  6143. {
  6144. auto &m = *sndFile->Patterns[pat].GetpModCommand(row, chn);
  6145. bool found;
  6146. switch(column)
  6147. {
  6148. case PatternCursor::noteColumn:
  6149. found = m.note != NOTE_NONE;
  6150. break;
  6151. case PatternCursor::instrColumn:
  6152. found = m.instr != 0;
  6153. break;
  6154. case PatternCursor::volumeColumn:
  6155. found = m.volcmd != VOLCMD_NONE;
  6156. break;
  6157. case PatternCursor::effectColumn:
  6158. case PatternCursor::paramColumn:
  6159. found = m.command != CMD_NONE;
  6160. break;
  6161. default:
  6162. found = false;
  6163. }
  6164. if(found)
  6165. {
  6166. if(select)
  6167. {
  6168. CursorJump(static_cast<int>(row) - m_Cursor.GetRow(), false);
  6169. } else
  6170. {
  6171. SetCurrentOrder(ord);
  6172. SetCurrentPattern(pat, row);
  6173. if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
  6174. {
  6175. PatternStep(row);
  6176. }
  6177. }
  6178. return;
  6179. }
  6180. row += direction;
  6181. }
  6182. // Continue search in prev/next pattern (unless we also select - selections cannot span multiple patterns)
  6183. if(select)
  6184. return;
  6185. ORDERINDEX nextOrd = nextEntry ? order.GetNextOrderIgnoringSkips(ord) : order.GetPreviousOrderIgnoringSkips(ord);
  6186. pat = order[nextOrd];
  6187. if(nextOrd == ord || !sndFile->Patterns.IsValidPat(pat))
  6188. return;
  6189. ord = nextOrd;
  6190. row = nextEntry ? 0 : (sndFile->Patterns[pat].GetNumRows() - 1);
  6191. }
  6192. }
  6193. // Copy to clipboard
  6194. bool CViewPattern::CopyPattern(PATTERNINDEX nPattern, const PatternRect &selection)
  6195. {
  6196. BeginWaitCursor();
  6197. bool result = PatternClipboard::Copy(*GetSoundFile(), nPattern, selection);
  6198. EndWaitCursor();
  6199. PatternClipboardDialog::UpdateList();
  6200. return result;
  6201. }
  6202. // Paste from clipboard
  6203. bool CViewPattern::PastePattern(PATTERNINDEX nPattern, const PatternCursor &pastePos, PatternClipboard::PasteModes mode)
  6204. {
  6205. BeginWaitCursor();
  6206. PatternEditPos pos;
  6207. pos.pattern = nPattern;
  6208. pos.row = pastePos.GetRow();
  6209. pos.channel = pastePos.GetChannel();
  6210. pos.order = GetCurrentOrder();
  6211. PatternRect rect;
  6212. const bool patternExisted = GetSoundFile()->Patterns.IsValidPat(nPattern);
  6213. bool orderChanged = false;
  6214. bool result = PatternClipboard::Paste(*GetSoundFile(), pos, mode, rect, orderChanged);
  6215. EndWaitCursor();
  6216. PatternHint updateHint = PatternHint(PATTERNINDEX_INVALID).Data();
  6217. if(pos.pattern != nPattern)
  6218. {
  6219. // Multipaste: Switch to pasted pattern.
  6220. SetCurrentPattern(pos.pattern);
  6221. SetCurrentOrder(pos.order);
  6222. }
  6223. if(orderChanged || (patternExisted != GetSoundFile()->Patterns.IsValidPat(nPattern)))
  6224. {
  6225. updateHint.Names();
  6226. GetDocument()->UpdateAllViews(nullptr, SequenceHint(GetSoundFile()->Order.GetCurrentSequenceIndex()).Data(), nullptr);
  6227. }
  6228. if(result)
  6229. {
  6230. SetCurSel(rect);
  6231. GetDocument()->SetModified();
  6232. GetDocument()->UpdateAllViews(nullptr, updateHint, nullptr);
  6233. }
  6234. return result;
  6235. }
  6236. template<typename Func>
  6237. void CViewPattern::ApplyToSelection(Func func)
  6238. {
  6239. CSoundFile *sndFile = GetSoundFile();
  6240. if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
  6241. return;
  6242. auto &pattern = sndFile->Patterns[m_nPattern];
  6243. m_Selection.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
  6244. const CHANNELINDEX startChn = m_Selection.GetStartChannel(), endChn = m_Selection.GetEndChannel();
  6245. const ROWINDEX endRow = m_Selection.GetEndRow();
  6246. for(ROWINDEX row = m_Selection.GetStartRow(); row <= endRow; row++)
  6247. {
  6248. ModCommand *m = pattern.GetpModCommand(row, startChn);
  6249. for(CHANNELINDEX chn = startChn; chn <= endChn; chn++, m++)
  6250. {
  6251. func(*m, row, chn);
  6252. }
  6253. }
  6254. }
  6255. INT_PTR CViewPattern::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
  6256. {
  6257. CRect rect;
  6258. const auto item = GetDragItem(point, rect);
  6259. const auto value = item.Value();
  6260. const CSoundFile &sndFile = *GetSoundFile();
  6261. mpt::winstring text;
  6262. switch(item.Type())
  6263. {
  6264. case DragItem::PatternHeader:
  6265. {
  6266. text = _T("Show Pattern Properties");
  6267. auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(kcShowPatternProperties, 0);
  6268. if(!keyText.IsEmpty())
  6269. text += MPT_CFORMAT(" ({})")(keyText);
  6270. break;
  6271. }
  6272. case DragItem::ChannelHeader:
  6273. if(value < sndFile.GetNumChannels())
  6274. {
  6275. if(!sndFile.ChnSettings[value].szName.empty())
  6276. text = MPT_TFORMAT("{}: {}")(value + 1, mpt::ToWin(sndFile.GetCharsetInternal(), sndFile.ChnSettings[value].szName));
  6277. else
  6278. text = MPT_TFORMAT("Channel {}")(value + 1);
  6279. }
  6280. break;
  6281. case DragItem::PluginName:
  6282. if(value < sndFile.GetNumChannels())
  6283. {
  6284. PLUGINDEX mixPlug = sndFile.ChnSettings[value].nMixPlugin;
  6285. if(mixPlug && mixPlug <= MAX_MIXPLUGINS)
  6286. text = MPT_TFORMAT("{}: {}")(mixPlug, mpt::ToWin(sndFile.m_MixPlugins[mixPlug - 1].GetName()));
  6287. else
  6288. text = _T("No Plugin");
  6289. }
  6290. break;
  6291. }
  6292. if(text.empty())
  6293. return CScrollView::OnToolHitTest(point, pTI);
  6294. pTI->hwnd = m_hWnd;
  6295. pTI->uId = item.ToIntPtr();
  6296. pTI->rect = rect;
  6297. // MFC will free() the text
  6298. TCHAR *textP = static_cast<TCHAR *>(calloc(text.size() + 1, sizeof(TCHAR)));
  6299. std::copy(text.begin(), text.end(), textP);
  6300. pTI->lpszText = textP;
  6301. return item.ToIntPtr();
  6302. }
  6303. // Accessible description for screen readers
  6304. HRESULT CViewPattern::get_accName(VARIANT varChild, BSTR *pszName)
  6305. {
  6306. const ModCommand &m = GetCursorCommand();
  6307. const size_t columnIndex = m_Cursor.GetColumnType();
  6308. const TCHAR *column = _T("");
  6309. static constexpr const TCHAR *regularColumns[] = {_T("Note"), _T("Instrument"), _T("Volume"), _T("Effect"), _T("Parameter")};
  6310. static constexpr const TCHAR *pcColumns[] = {_T("Note"), _T("Plugin"), _T("Plugin Parameter"), _T("Parameter Value"), _T("Parameter Value")};
  6311. static_assert(PatternCursor::lastColumn + 1 == std::size(regularColumns));
  6312. static_assert(PatternCursor::lastColumn + 1 == std::size(pcColumns));
  6313. if(m.IsPcNote() && columnIndex < std::size(pcColumns))
  6314. column = pcColumns[columnIndex];
  6315. else if(!m.IsPcNote() && columnIndex < std::size(regularColumns))
  6316. column = regularColumns[columnIndex];
  6317. const CSoundFile *sndFile = GetSoundFile();
  6318. const CHANNELINDEX chn = m_Cursor.GetChannel();
  6319. const auto channelNumber = mpt::cfmt::val(chn + 1);
  6320. CString channelName = channelNumber;
  6321. if(chn < sndFile->GetNumChannels() && !sndFile->ChnSettings[chn].szName.empty())
  6322. channelName += _T(": ") + mpt::ToCString(sndFile->GetCharsetInternal(), sndFile->ChnSettings[chn].szName);
  6323. CString str = TrackerSettings::Instance().patternAccessibilityFormat;
  6324. str.Replace(_T("%sequence%"), mpt::cfmt::val(sndFile->Order.GetCurrentSequenceIndex()));
  6325. str.Replace(_T("%order%"), mpt::cfmt::val(GetCurrentOrder()));
  6326. str.Replace(_T("%pattern%"), mpt::cfmt::val(GetCurrentPattern()));
  6327. str.Replace(_T("%row%"), mpt::cfmt::val(m_Cursor.GetRow()));
  6328. str.Replace(_T("%channel%"), channelNumber);
  6329. str.Replace(_T("%column_type%"), column);
  6330. str.Replace(_T("%column_description%"), GetCursorDescription());
  6331. str.Replace(_T("%channel_name%"), channelName);
  6332. if(str.IsEmpty())
  6333. return CModScrollView::get_accName(varChild, pszName);
  6334. *pszName = str.AllocSysString();
  6335. return S_OK;
  6336. }
  6337. OPENMPT_NAMESPACE_END