123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320 |
- /*
- * View_pat.cpp
- * ------------
- * Purpose: Pattern tab, lower panel.
- * Notes : Welcome to about 7000 lines of, err, very beautiful code.
- * Authors: Olivier Lapicque
- * OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Mptrack.h"
- #include "Mainfrm.h"
- #include "InputHandler.h"
- #include "Childfrm.h"
- #include "Moddoc.h"
- #include "SampleEditorDialogs.h" // For amplification dialog (which is re-used from sample editor)
- #include "Globals.h"
- #include "View_pat.h"
- #include "Ctrl_pat.h"
- #include "PatternFont.h"
- #include "PatternFindReplace.h"
- #include "PatternFindReplaceDlg.h"
- #include "EffectVis.h"
- #include "PatternGotoDialog.h"
- #include "MIDIMacros.h"
- #include "../common/misc_util.h"
- #include "../soundlib/MIDIEvents.h"
- #include "../soundlib/mod_specifications.h"
- #include "../soundlib/plugins/PlugInterface.h"
- #include <algorithm>
- OPENMPT_NAMESPACE_BEGIN
- // Static initializers
- ModCommand CViewPattern::m_cmdOld = ModCommand::Empty();
- int32 CViewPattern::m_nTransposeAmount = 1;
- IMPLEMENT_SERIAL(CViewPattern, CModScrollView, 0)
- BEGIN_MESSAGE_MAP(CViewPattern, CModScrollView)
- //{{AFX_MSG_MAP(CViewPattern)
- ON_WM_ERASEBKGND()
- ON_WM_VSCROLL()
- ON_WM_SIZE()
- ON_WM_MOUSEWHEEL()
- ON_WM_XBUTTONUP()
- ON_WM_MOUSEMOVE()
- ON_WM_LBUTTONDOWN()
- ON_WM_LBUTTONDBLCLK()
- ON_WM_LBUTTONUP()
- ON_WM_RBUTTONDOWN()
- ON_WM_SETFOCUS()
- ON_WM_KILLFOCUS()
- ON_WM_SYSKEYDOWN()
- ON_WM_DESTROY()
- ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewPattern::OnCustomKeyMsg)
- ON_MESSAGE(WM_MOD_MIDIMSG, &CViewPattern::OnMidiMsg)
- ON_MESSAGE(WM_MOD_RECORDPARAM, &CViewPattern::OnRecordPlugParamChange)
- ON_COMMAND(ID_EDIT_CUT, &CViewPattern::OnEditCut)
- ON_COMMAND(ID_EDIT_COPY, &CViewPattern::OnEditCopy)
- ON_COMMAND(ID_EDIT_PASTE, &CViewPattern::OnEditPaste)
- ON_COMMAND(ID_EDIT_MIXPASTE, &CViewPattern::OnEditMixPaste)
- ON_COMMAND(ID_EDIT_MIXPASTE_ITSTYLE,&CViewPattern::OnEditMixPasteITStyle)
- ON_COMMAND(ID_EDIT_PASTEFLOOD, &CViewPattern::OnEditPasteFlood)
- ON_COMMAND(ID_EDIT_PUSHFORWARDPASTE,&CViewPattern::OnEditPushForwardPaste)
- ON_COMMAND(ID_EDIT_SELECT_ALL, &CViewPattern::OnEditSelectAll)
- ON_COMMAND(ID_EDIT_SELECTCOLUMN,&CViewPattern::OnEditSelectChannel)
- ON_COMMAND(ID_EDIT_SELECTCOLUMN2,&CViewPattern::OnSelectCurrentChannel)
- ON_COMMAND(ID_EDIT_FIND, &CViewPattern::OnEditFind)
- ON_COMMAND(ID_EDIT_GOTO_MENU, &CViewPattern::OnEditGoto)
- ON_COMMAND(ID_EDIT_FINDNEXT, &CViewPattern::OnEditFindNext)
- ON_COMMAND(ID_EDIT_RECSELECT, &CViewPattern::OnRecordSelect)
- ON_COMMAND(ID_EDIT_SPLITRECSELECT, &CViewPattern::OnSplitRecordSelect)
- ON_COMMAND(ID_EDIT_SPLITKEYBOARDSETTINGS, &CViewPattern::SetSplitKeyboardSettings)
- ON_COMMAND(ID_EDIT_UNDO, &CViewPattern::OnEditUndo)
- ON_COMMAND(ID_EDIT_REDO, &CViewPattern::OnEditRedo)
- ON_COMMAND(ID_PATTERN_CHNRESET, &CViewPattern::OnChannelReset)
- ON_COMMAND(ID_PATTERN_MUTE, &CViewPattern::OnMuteFromClick)
- ON_COMMAND(ID_PATTERN_SOLO, &CViewPattern::OnSoloFromClick)
- ON_COMMAND(ID_PATTERN_TRANSITIONMUTE, &CViewPattern::OnTogglePendingMuteFromClick)
- ON_COMMAND(ID_PATTERN_TRANSITIONSOLO, &CViewPattern::OnPendingSoloChnFromClick)
- ON_COMMAND(ID_PATTERN_TRANSITION_UNMUTEALL, &CViewPattern::OnPendingUnmuteAllChnFromClick)
- ON_COMMAND(ID_PATTERN_UNMUTEALL,&CViewPattern::OnUnmuteAll)
- ON_COMMAND(ID_PATTERN_SPLIT, &CViewPattern::OnSplitPattern)
- ON_COMMAND(ID_NEXTINSTRUMENT, &CViewPattern::OnNextInstrument)
- ON_COMMAND(ID_PREVINSTRUMENT, &CViewPattern::OnPrevInstrument)
- ON_COMMAND(ID_PATTERN_PLAYROW, &CViewPattern::OnPatternStep)
- ON_COMMAND(IDC_PATTERN_RECORD, &CViewPattern::OnPatternRecord)
- ON_COMMAND(ID_PATTERN_DELETEROW, &CViewPattern::OnDeleteRow)
- ON_COMMAND(ID_PATTERN_DELETEALLROW, &CViewPattern::OnDeleteWholeRow)
- ON_COMMAND(ID_PATTERN_DELETEROWGLOBAL, &CViewPattern::OnDeleteRowGlobal)
- ON_COMMAND(ID_PATTERN_DELETEALLROWGLOBAL, &CViewPattern::OnDeleteWholeRowGlobal)
- ON_COMMAND(ID_PATTERN_INSERTROW, &CViewPattern::OnInsertRow)
- ON_COMMAND(ID_PATTERN_INSERTALLROW, &CViewPattern::OnInsertWholeRow)
- ON_COMMAND(ID_PATTERN_INSERTROWGLOBAL, &CViewPattern::OnInsertRowGlobal)
- ON_COMMAND(ID_PATTERN_INSERTALLROWGLOBAL, &CViewPattern::OnInsertWholeRowGlobal)
- ON_COMMAND(ID_RUN_SCRIPT, &CViewPattern::OnRunScript)
- ON_COMMAND(ID_TRANSPOSE_UP, &CViewPattern::OnTransposeUp)
- ON_COMMAND(ID_TRANSPOSE_DOWN, &CViewPattern::OnTransposeDown)
- ON_COMMAND(ID_TRANSPOSE_OCTUP, &CViewPattern::OnTransposeOctUp)
- ON_COMMAND(ID_TRANSPOSE_OCTDOWN, &CViewPattern::OnTransposeOctDown)
- ON_COMMAND(ID_TRANSPOSE_CUSTOM, &CViewPattern::OnTransposeCustom)
- ON_COMMAND(ID_PATTERN_PROPERTIES, &CViewPattern::OnPatternProperties)
- ON_COMMAND(ID_PATTERN_INTERPOLATE_VOLUME, &CViewPattern::OnInterpolateVolume)
- ON_COMMAND(ID_PATTERN_INTERPOLATE_EFFECT, &CViewPattern::OnInterpolateEffect)
- ON_COMMAND(ID_PATTERN_INTERPOLATE_NOTE, &CViewPattern::OnInterpolateNote)
- ON_COMMAND(ID_PATTERN_INTERPOLATE_INSTR, &CViewPattern::OnInterpolateInstr)
- ON_COMMAND(ID_PATTERN_VISUALIZE_EFFECT, &CViewPattern::OnVisualizeEffect)
- ON_COMMAND(ID_GROW_SELECTION, &CViewPattern::OnGrowSelection)
- ON_COMMAND(ID_SHRINK_SELECTION, &CViewPattern::OnShrinkSelection)
- ON_COMMAND(ID_PATTERN_SETINSTRUMENT, &CViewPattern::OnSetSelInstrument)
- ON_COMMAND(ID_PATTERN_ADDCHANNEL_FRONT, &CViewPattern::OnAddChannelFront)
- ON_COMMAND(ID_PATTERN_ADDCHANNEL_AFTER, &CViewPattern::OnAddChannelAfter)
- ON_COMMAND(ID_PATTERN_RESETCHANNELCOLORS, &CViewPattern::OnResetChannelColors)
- ON_COMMAND(ID_PATTERN_TRANSPOSECHANNEL, &CViewPattern::OnTransposeChannel)
- ON_COMMAND(ID_PATTERN_DUPLICATECHANNEL, &CViewPattern::OnDuplicateChannel)
- ON_COMMAND(ID_PATTERN_REMOVECHANNEL, &CViewPattern::OnRemoveChannel)
- ON_COMMAND(ID_PATTERN_REMOVECHANNELDIALOG, &CViewPattern::OnRemoveChannelDialog)
- ON_COMMAND(ID_PATTERN_AMPLIFY, &CViewPattern::OnPatternAmplify)
- ON_COMMAND(ID_CLEAR_SELECTION, &CViewPattern::OnClearSelectionFromMenu)
- ON_COMMAND(ID_SHOWTIMEATROW, &CViewPattern::OnShowTimeAtRow)
- ON_COMMAND(ID_PATTERN_EDIT_PCNOTE_PLUGIN, &CViewPattern::OnTogglePCNotePluginEditor)
- ON_COMMAND(ID_SETQUANTIZE, &CViewPattern::OnSetQuantize)
- ON_COMMAND(ID_LOCK_PATTERN_ROWS, &CViewPattern::OnLockPatternRows)
- ON_COMMAND_RANGE(ID_CHANGE_INSTRUMENT, ID_CHANGE_INSTRUMENT+MAX_INSTRUMENTS, &CViewPattern::OnSelectInstrument)
- ON_COMMAND_RANGE(ID_CHANGE_PCNOTE_PARAM, ID_CHANGE_PCNOTE_PARAM + ModCommand::maxColumnValue, &CViewPattern::OnSelectPCNoteParam)
- ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewPattern::OnUpdateUndo)
- ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewPattern::OnUpdateRedo)
- ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT+MAX_MIXPLUGINS, &CViewPattern::OnSelectPlugin)
- //}}AFX_MSG_MAP
- ON_WM_INITMENU()
- ON_WM_RBUTTONDBLCLK()
- ON_WM_RBUTTONUP()
- END_MESSAGE_MAP()
- static_assert(ModCommand::maxColumnValue <= 999, "Command range for ID_CHANGE_PCNOTE_PARAM is designed for 999");
- const CSoundFile *CViewPattern::GetSoundFile() const { return (GetDocument() != nullptr) ? &GetDocument()->GetSoundFile() : nullptr; };
- CSoundFile *CViewPattern::GetSoundFile() { return (GetDocument() != nullptr) ? &GetDocument()->GetSoundFile() : nullptr; };
- const ModSequence &CViewPattern::Order() const { return GetSoundFile()->Order(); }
- ModSequence &CViewPattern::Order() { return GetSoundFile()->Order(); }
- CViewPattern::CViewPattern()
- {
- EnableActiveAccessibility();
- m_Dib.Init(CMainFrame::bmpNotes);
- UpdateColors();
- m_PCNoteEditMemory = ModCommand::Empty();
- m_octaveKeyMemory.fill(NOTE_NONE);
- }
- CViewPattern::~CViewPattern()
- {
- m_offScreenBitmap.DeleteObject();
- m_offScreenDC.DeleteDC();
- }
- void CViewPattern::OnInitialUpdate()
- {
- CModScrollView::OnInitialUpdate();
- EnableToolTips();
- ChnVUMeters.fill(0);
- OldVUMeters.fill(0);
- m_previousNote.fill(NOTE_NONE);
- m_splitActiveNoteChannel.fill(NOTE_CHANNEL_MAP_INVALID);
- m_activeNoteChannel.fill(NOTE_CHANNEL_MAP_INVALID);
- m_nPlayPat = PATTERNINDEX_INVALID;
- m_nPlayRow = m_nNextPlayRow = 0;
- m_nPlayTick = 0;
- m_nTicksOnRow = 1;
- m_nMidRow = 0;
- m_nDragItem = {};
- m_bInItemRect = false;
- m_bContinueSearch = false;
- m_Status = psShowPluginNames;
- m_nXScroll = m_nYScroll = 0;
- m_nPattern = 0;
- m_nSpacing = 0;
- m_nAccelChar = 0;
- PatternFont::UpdateFont(m_hWnd);
- UpdateSizes();
- UpdateScrollSize();
- SetCurrentPattern(0);
- m_fallbackInstrument = 0;
- m_nLastPlayedRow = 0;
- m_nLastPlayedOrder = ORDERINDEX_INVALID;
- m_prevChordNote = NOTE_NONE;
- }
- bool CViewPattern::SetCurrentPattern(PATTERNINDEX pat, ROWINDEX row)
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr)
- return false;
- if(pat == pSndFile->Order.GetIgnoreIndex() || pat == pSndFile->Order.GetInvalidPatIndex())
- return false;
- if(m_pEditWnd && m_pEditWnd->IsWindowVisible())
- m_pEditWnd->ShowWindow(SW_HIDE);
- m_nPattern = pat;
- bool updateScroll = false;
- if(pSndFile->Patterns.IsValidPat(pat))
- {
- if(row != ROWINDEX_INVALID && row != GetCurrentRow() && row < pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- m_Cursor.SetRow(row);
- updateScroll = true;
- }
- if(GetCurrentRow() >= pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- m_Cursor.SetRow(0);
- updateScroll = true;
- }
- }
- SetSelToCursor();
- UpdateSizes();
- UpdateScrollSize();
- UpdateIndicator();
- if(m_bWholePatternFitsOnScreen)
- SetScrollPos(SB_VERT, 0);
- else if(updateScroll)
- SetScrollPos(SB_VERT, (int)GetCurrentRow() * GetRowHeight());
- UpdateScrollPos();
- InvalidatePattern(true, true);
- SendCtrlMessage(CTRLMSG_PATTERNCHANGED, m_nPattern);
- return true;
- }
- // This should be used instead of consecutive calls to SetCurrentRow() then SetCurrentColumn().
- bool CViewPattern::SetCursorPosition(const PatternCursor &cursor, bool wrap)
- {
- // Set row, but do not update scroll position yet
- // as there is another position update on the way:
- SetCurrentRow(cursor.GetRow(), wrap, false);
- // Now set column and update scroll position:
- SetCurrentColumn(cursor);
- return true;
- }
- ROWINDEX CViewPattern::SetCurrentRow(ROWINDEX row, bool wrap, bool updateHorizontalScrollbar)
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidIndex(m_nPattern))
- return ROWINDEX_INVALID;
- if(wrap && pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- const auto &order = Order();
- if((int)row < 0)
- {
- if(m_Status[psKeyboardDragSelect | psMouseDragSelect])
- {
- row = 0;
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL)
- {
- ORDERINDEX curOrder = GetCurrentOrder();
- const ORDERINDEX prevOrd = order.GetPreviousOrderIgnoringSkips(curOrder);
- if(prevOrd < curOrder && m_nPattern == order[curOrder])
- {
- const PATTERNINDEX nPrevPat = order[prevOrd];
- if((nPrevPat < pSndFile->Patterns.Size()) && (pSndFile->Patterns[nPrevPat].GetNumRows()))
- {
- SetCurrentOrder(prevOrd);
- if(SetCurrentPattern(nPrevPat))
- return SetCurrentRow(pSndFile->Patterns[nPrevPat].GetNumRows() + (int)row);
- }
- }
- row = 0;
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)
- {
- if((int)row < (int)0)
- row += pSndFile->Patterns[m_nPattern].GetNumRows();
- row %= pSndFile->Patterns[m_nPattern].GetNumRows();
- }
- } else //row >= 0
- if(row >= pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- if(m_Status[psKeyboardDragSelect | psMouseDragSelect])
- {
- row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL)
- {
- ORDERINDEX curOrder = GetCurrentOrder();
- ORDERINDEX nextOrder = order.GetNextOrderIgnoringSkips(curOrder);
- if(nextOrder > curOrder && m_nPattern == order[curOrder])
- {
- PATTERNINDEX nextPat = order[nextOrder];
- if((nextPat < pSndFile->Patterns.Size()) && (pSndFile->Patterns[nextPat].GetNumRows()))
- {
- const ROWINDEX newRow = row - pSndFile->Patterns[m_nPattern].GetNumRows();
- SetCurrentOrder(nextOrder);
- if(SetCurrentPattern(nextPat))
- return SetCurrentRow(newRow);
- }
- }
- row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)
- {
- row %= pSndFile->Patterns[m_nPattern].GetNumRows();
- }
- }
- }
- if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
- {
- if(static_cast<int>(row) < 0)
- row = 0;
- if(row >= pSndFile->Patterns[m_nPattern].GetNumRows())
- row = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
- }
- if((row >= pSndFile->Patterns[m_nPattern].GetNumRows()) || (!m_szCell.cy))
- return false;
- // Fix: If cursor isn't on screen move both scrollbars to make it visible
- InvalidateRow();
- m_Cursor.SetRow(row);
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- UpdateScrollbarPositions(updateHorizontalScrollbar);
- InvalidateRow();
- PatternCursor selStart(m_Cursor);
- if(m_Status[psKeyboardDragSelect | psMouseDragSelect] && !m_Status[psDragnDropEdit])
- {
- selStart.Set(m_StartSel);
- }
- SetCurSel(selStart, m_Cursor);
- return row;
- }
- bool CViewPattern::SetCurrentColumn(CHANNELINDEX channel, PatternCursor::Columns column)
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr)
- {
- return false;
- }
- LimitMax(column, m_nDetailLevel);
- m_Cursor.SetColumn(channel, column);
- PatternCursor selStart(m_Cursor);
- if(m_Status[psKeyboardDragSelect | psMouseDragSelect] && !m_Status[psDragnDropEdit])
- {
- selStart = m_StartSel;
- }
- SetCurSel(selStart, m_Cursor);
- // Fix: If cursor isn't on screen move both scrollbars to make it visible
- UpdateScrollbarPositions();
- return true;
- }
- // Set document as modified and optionally update all pattern views.
- void CViewPattern::SetModified(bool updateAllViews)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc != nullptr)
- {
- pModDoc->SetModified();
- pModDoc->UpdateAllViews(this, PatternHint(m_nPattern).Data(), updateAllViews ? nullptr : this);
- }
- CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this);
- }
- // Fix: If cursor isn't on screen move scrollbars to make it visible
- // Fix: save pattern scrollbar position when switching to other tab
- // Assume that m_nRow and m_dwCursor are valid
- // When we switching to other tab the CViewPattern object is deleted
- // and when switching back new one is created
- bool CViewPattern::UpdateScrollbarPositions(bool updateHorizontalScrollbar)
- {
- // HACK - after new CViewPattern object created SetCurrentRow() and SetCurrentColumn() are called -
- // just skip first two calls of UpdateScrollbarPositions() if pModDoc->GetOldPatternScrollbarsPos() is valid
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- CSize scroll = pModDoc->GetOldPatternScrollbarsPos();
- if(scroll.cx >= 0)
- {
- OnScrollBy(scroll);
- scroll.cx = -1;
- pModDoc->SetOldPatternScrollbarsPos(scroll);
- return true;
- } else if(scroll.cx >= -1)
- {
- scroll.cx = -2;
- pModDoc->SetOldPatternScrollbarsPos(scroll);
- return true;
- }
- }
- CSize scroll(0, 0);
- UINT row = GetCurrentRow();
- UINT yofs = GetYScrollPos();
- CRect rect;
- GetClientRect(&rect);
- rect.top += m_szHeader.cy;
- int numrows = (rect.bottom - rect.top - 1) / m_szCell.cy;
- if(numrows < 1)
- numrows = 1;
- if(m_nMidRow)
- {
- if(row != yofs)
- {
- scroll.cy = (int)(row - yofs) * m_szCell.cy;
- }
- } else
- {
- if(row < yofs)
- {
- scroll.cy = (int)(row - yofs) * m_szCell.cy;
- } else if(row > yofs + (UINT)numrows - 1)
- {
- scroll.cy = (int)(row - yofs - numrows + 1) * m_szCell.cy;
- }
- }
- if(updateHorizontalScrollbar)
- {
- UINT xofs = GetXScrollPos();
- const CHANNELINDEX nchn = GetCurrentChannel();
- if(nchn < xofs)
- {
- scroll.cx = (int)(xofs - nchn) * m_szCell.cx;
- scroll.cx *= -1;
- } else if(nchn > xofs)
- {
- int maxcol = (rect.right - m_szHeader.cx) / m_szCell.cx;
- if((nchn >= (xofs + maxcol)) && (maxcol >= 0))
- {
- scroll.cx = (int)(nchn - xofs - maxcol + 1) * m_szCell.cx;
- }
- }
- }
- if(scroll.cx != 0 || scroll.cy != 0)
- {
- OnScrollBy(scroll, TRUE);
- }
- return true;
- }
- DragItem CViewPattern::GetDragItem(CPoint point, RECT &outRect) const
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr)
- return {};
- CRect rcClient, rect, plugRect;
- GetClientRect(&rcClient);
- rect.SetRect(m_szHeader.cx, 0, m_szHeader.cx + GetChannelWidth(), m_szHeader.cy);
- plugRect.SetRect(m_szHeader.cx, m_szHeader.cy - m_szPluginHeader.cy, m_szHeader.cx + GetChannelWidth(), m_szHeader.cy);
- const auto xOffset = static_cast<CHANNELINDEX>(GetXScrollPos());
- const CHANNELINDEX numChannels = pSndFile->GetNumChannels();
- // Checking channel headers
- if(m_Status[psShowPluginNames])
- {
- for(CHANNELINDEX n = xOffset; n < numChannels; n++)
- {
- if(plugRect.PtInRect(point))
- {
- outRect = plugRect;
- return {DragItem::PluginName, n};
- }
- plugRect.OffsetRect(GetChannelWidth(), 0);
- }
- }
- for(CHANNELINDEX n = xOffset; n < numChannels; n++)
- {
- if(rect.PtInRect(point))
- {
- outRect = rect;
- return {DragItem::ChannelHeader, n};
- }
- rect.OffsetRect(GetChannelWidth(), 0);
- }
- if(pSndFile->Patterns.IsValidPat(m_nPattern) && (pSndFile->GetType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT)))
- {
- // Clicking on upper-left corner with pattern number (for pattern properties)
- rect.SetRect(0, 0, m_szHeader.cx, m_szHeader.cy);
- if(rect.PtInRect(point))
- {
- outRect = rect;
- return {DragItem::PatternHeader, 0};
- }
- }
- return {};
- }
- // Drag a selection to position "cursor".
- // If scrollHorizontal is true, the point's channel is ensured to be visible.
- // Likewise, if scrollVertical is true, the point's row is ensured to be visible.
- // If noMode if specified, the original selection points are not altered.
- bool CViewPattern::DragToSel(const PatternCursor &cursor, bool scrollHorizontal, bool scrollVertical, bool noMove)
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- return false;
- CRect rect;
- int yofs = GetYScrollPos(), xofs = GetXScrollPos();
- int row, col;
- if(!m_szCell.cy)
- return false;
- GetClientRect(&rect);
- if(!noMove)
- SetCurSel(m_StartSel, cursor);
- if(!scrollHorizontal && !scrollVertical)
- return true;
- // Scroll to row
- row = cursor.GetRow();
- if(scrollVertical && row < (int)pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- row += m_nMidRow;
- rect.top += m_szHeader.cy;
- int numrows = (rect.bottom - rect.top - 1) / m_szCell.cy;
- if(numrows < 1)
- numrows = 1;
- if(row < yofs)
- {
- CSize sz;
- sz.cx = 0;
- sz.cy = (int)(row - yofs) * m_szCell.cy;
- OnScrollBy(sz, TRUE);
- } else if(row > yofs + numrows - 1)
- {
- CSize sz;
- sz.cx = 0;
- sz.cy = (int)(row - yofs - numrows + 1) * m_szCell.cy;
- OnScrollBy(sz, TRUE);
- }
- }
- // Scroll to column
- col = cursor.GetChannel();
- if(scrollHorizontal && col < (int)pSndFile->GetNumChannels())
- {
- int maxcol = (rect.right - m_szHeader.cx) - 4;
- maxcol -= GetColumnOffset(cursor.GetColumnType());
- maxcol /= GetChannelWidth();
- if(col < xofs)
- {
- CSize size(-m_szCell.cx, 0);
- if(!noMove)
- size.cx = (col - xofs) * (int)m_szCell.cx;
- OnScrollBy(size, TRUE);
- } else if((col > xofs + maxcol) && (maxcol > 0))
- {
- CSize size(m_szCell.cx, 0);
- if(!noMove)
- size.cx = (col - maxcol + 1) * (int)m_szCell.cx;
- OnScrollBy(size, TRUE);
- }
- }
- UpdateWindow();
- return true;
- }
- bool CViewPattern::SetPlayCursor(PATTERNINDEX pat, ROWINDEX row, uint32 tick)
- {
- PATTERNINDEX oldPat = m_nPlayPat;
- ROWINDEX oldRow = m_nPlayRow;
- uint32 oldTick = m_nPlayTick;
- m_nPlayPat = pat;
- m_nPlayRow = row;
- m_nPlayTick = tick;
- if(m_nPlayTick != oldTick && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SMOOTHSCROLL))
- InvalidatePattern(true, true);
- else if(oldPat == m_nPattern)
- InvalidateRow(oldRow);
- else if(m_nPlayPat == m_nPattern)
- InvalidateRow(m_nPlayRow);
- return true;
- }
- UINT CViewPattern::GetCurrentInstrument() const
- {
- return static_cast<UINT>(SendCtrlMessage(CTRLMSG_GETCURRENTINSTRUMENT));
- }
- bool CViewPattern::ShowEditWindow()
- {
- if(!m_pEditWnd)
- {
- m_pEditWnd = new CEditCommand(*GetSoundFile());
- }
- if(m_pEditWnd)
- {
- m_pEditWnd->ShowEditWindow(m_nPattern, m_Cursor, this);
- return true;
- }
- return false;
- }
- bool CViewPattern::PrepareUndo(const PatternCursor &beginSel, const PatternCursor &endSel, const char *description)
- {
- CModDoc *pModDoc = GetDocument();
- const CHANNELINDEX chnBegin = beginSel.GetChannel(), chnEnd = endSel.GetChannel();
- const ROWINDEX rowBegin = beginSel.GetRow(), rowEnd = endSel.GetRow();
- if((chnEnd < chnBegin) || (rowEnd < rowBegin) || pModDoc == nullptr)
- return false;
- return pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, chnBegin, rowBegin, chnEnd - chnBegin + 1, rowEnd - rowBegin + 1, description);
- }
- BOOL CViewPattern::PreTranslateMessage(MSG *pMsg)
- {
- if(pMsg)
- {
- //We handle keypresses before Windows has a chance to handle them (for alt etc..)
- if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) ||
- (pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN))
- {
- CInputHandler *ih = CMainFrame::GetInputHandler();
- //Translate message manually
- UINT nChar = static_cast<UINT>(pMsg->wParam);
- UINT nRepCnt = LOWORD(pMsg->lParam);
- UINT nFlags = HIWORD(pMsg->lParam);
- KeyEventType kT = ih->GetKeyEventType(nFlags);
- InputTargetContext ctx = (InputTargetContext)(kCtxViewPatterns + 1 + m_Cursor.GetColumnType());
- // If editing is disabled, preview notes no matter which column we are in
- if(!IsEditingEnabled() && TrackerSettings::Instance().patternNoEditPopup)
- ctx = kCtxViewPatternsNote;
- if(ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull)
- {
- return true; // Mapped to a command, no need to pass message on.
- }
- //HACK: fold kCtxViewPatternsFX and kCtxViewPatternsFXparam so that all commands of 1 are active in the other
- else
- {
- if(ctx == kCtxViewPatternsFX)
- {
- if(ih->KeyEvent(kCtxViewPatternsFXparam, nChar, nRepCnt, nFlags, kT) != kcNull)
- return true; // Mapped to a command, no need to pass message on.
- } else if(ctx == kCtxViewPatternsFXparam)
- {
- if(ih->KeyEvent(kCtxViewPatternsFX, nChar, nRepCnt, nFlags, kT) != kcNull)
- return true; // Mapped to a command, no need to pass message on.
- } else if(ctx == kCtxViewPatternsIns)
- {
- // Do the same with instrument->note column
- if(ih->KeyEvent(kCtxViewPatternsNote, nChar, nRepCnt, nFlags, kT) != kcNull)
- return true; // Mapped to a command, no need to pass message on.
- }
- }
- //end HACK.
- // Handle Application (menu) key
- if(pMsg->message == WM_KEYDOWN && nChar == VK_APPS)
- {
- OnRButtonDown(0, GetPointFromPosition(m_Cursor));
- }
- } else if(pMsg->message == WM_MBUTTONDOWN)
- {
- // Open quick channel properties dialog if we're middle-clicking a channel header.
- CPoint point(GET_X_LPARAM(pMsg->lParam), GET_Y_LPARAM(pMsg->lParam));
- if(point.y < m_szHeader.cy - m_szPluginHeader.cy)
- {
- PatternCursor cursor = GetPositionFromPoint(point);
- if(cursor.GetChannel() < GetDocument()->GetNumChannels())
- {
- ClientToScreen(&point);
- m_quickChannelProperties.Show(GetDocument(), cursor.GetChannel(), point);
- return true;
- }
- }
- }
- }
- return CModScrollView::PreTranslateMessage(pMsg);
- }
- ////////////////////////////////////////////////////////////////////////
- // CViewPattern message handlers
- void CViewPattern::OnDestroy()
- {
- // Fix: save pattern scrollbar position when switching to other tab
- // When we switching to other tab the CViewPattern object is deleted
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- pModDoc->SetOldPatternScrollbarsPos(CSize(m_nXScroll * m_szCell.cx, m_nYScroll * m_szCell.cy));
- }
- if(m_pEffectVis)
- {
- m_pEffectVis->DoClose();
- m_pEffectVis = nullptr;
- }
- if(m_pEditWnd)
- {
- m_pEditWnd->DestroyWindow();
- delete m_pEditWnd;
- m_pEditWnd = NULL;
- }
- CModScrollView::OnDestroy();
- }
- void CViewPattern::OnSetFocus(CWnd *pOldWnd)
- {
- CScrollView::OnSetFocus(pOldWnd);
- m_Status.set(psFocussed);
- InvalidateRow();
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
- pModDoc->SetFollowWnd(m_hWnd);
- UpdateIndicator();
- }
- }
- void CViewPattern::OnKillFocus(CWnd *pNewWnd)
- {
- CScrollView::OnKillFocus(pNewWnd);
- m_Status.reset(psKeyboardDragSelect | psCtrlDragSelect | psFocussed);
- InvalidateRow();
- }
- void CViewPattern::OnGrowSelection()
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- BeginWaitCursor();
- m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
- const PatternCursor startSel = m_Selection.GetUpperLeft();
- const PatternCursor endSel = m_Selection.GetLowerRight();
- PrepareUndo(startSel, PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows(), endSel), "Grow Selection");
- const ROWINDEX finalDest = m_Selection.GetStartRow() + (m_Selection.GetNumRows() - 1) * 2;
- for(int row = finalDest; row > (int)startSel.GetRow(); row -= 2)
- {
- if(ROWINDEX(row) >= pSndFile->Patterns[m_nPattern].GetNumRows())
- {
- continue;
- }
- int offset = row - startSel.GetRow();
- for(CHANNELINDEX chn = startSel.GetChannel(); chn <= endSel.GetChannel(); chn++)
- {
- for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
- {
- PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
- if(!m_Selection.ContainsHorizontal(cell))
- {
- // We might have to skip the first / last few entries.
- continue;
- }
- ModCommand *dest = pSndFile->Patterns[m_nPattern].GetpModCommand(row, chn);
- ModCommand *src = pSndFile->Patterns[m_nPattern].GetpModCommand(row - offset / 2, chn);
- ModCommand *blank = pSndFile->Patterns[m_nPattern].GetpModCommand(row - 1, chn); // Row "in between"
- switch(i)
- {
- case PatternCursor::noteColumn:
- dest->note = src->note;
- blank->note = NOTE_NONE;
- break;
- case PatternCursor::instrColumn:
- dest->instr = src->instr;
- blank->instr = 0;
- break;
- case PatternCursor::volumeColumn:
- dest->volcmd = src->volcmd;
- blank->volcmd = VOLCMD_NONE;
- dest->vol = src->vol;
- blank->vol = 0;
- break;
- case PatternCursor::effectColumn:
- dest->command = src->command;
- blank->command = CMD_NONE;
- break;
- case PatternCursor::paramColumn:
- dest->param = src->param;
- blank->param = 0;
- break;
- }
- }
- }
- }
- // Adjust selection
- m_Selection = PatternRect(startSel, PatternCursor(std::min(finalDest, static_cast<ROWINDEX>(pSndFile->Patterns[m_nPattern].GetNumRows() - 1)), endSel));
- InvalidatePattern();
- SetModified();
- EndWaitCursor();
- SetFocus();
- }
- void CViewPattern::OnShrinkSelection()
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- BeginWaitCursor();
- m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
- const PatternCursor startSel = m_Selection.GetUpperLeft();
- const PatternCursor endSel = m_Selection.GetLowerRight();
- PrepareUndo(startSel, endSel, "Shrink Selection");
- const ROWINDEX finalDest = m_Selection.GetStartRow() + (m_Selection.GetNumRows() - 1) / 2;
- for(ROWINDEX row = startSel.GetRow(); row <= endSel.GetRow(); row++)
- {
- const ROWINDEX offset = row - startSel.GetRow();
- const ROWINDEX srcRow = startSel.GetRow() + (offset * 2);
- for(CHANNELINDEX chn = startSel.GetChannel(); chn <= endSel.GetChannel(); chn++)
- {
- ModCommand *dest = pSndFile->Patterns[m_nPattern].GetpModCommand(row, chn);
- ModCommand src;
- if(row <= finalDest)
- {
- // Normal shrink operation
- src = *pSndFile->Patterns[m_nPattern].GetpModCommand(srcRow, chn);
- // If source command is empty, try next source row (so we don't lose all the stuff that's on odd rows).
- if(srcRow < pSndFile->Patterns[m_nPattern].GetNumRows() - 1)
- {
- const ModCommand &srcNext = *pSndFile->Patterns[m_nPattern].GetpModCommand(srcRow + 1, chn);
- if(src.note == NOTE_NONE)
- src.note = srcNext.note;
- if(src.instr == 0)
- src.instr = srcNext.instr;
- if(src.volcmd == VOLCMD_NONE)
- {
- src.volcmd = srcNext.volcmd;
- src.vol = srcNext.vol;
- }
- if(src.command == CMD_NONE)
- {
- src.command = srcNext.command;
- src.param = srcNext.param;
- }
- }
- } else
- {
- // Clean up rows that are now supposed to be empty.
- src = ModCommand::Empty();
- }
- for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
- {
- PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
- if(!m_Selection.ContainsHorizontal(cell))
- {
- // We might have to skip the first / last few entries.
- continue;
- }
- switch(i)
- {
- case PatternCursor::noteColumn:
- dest->note = src.note;
- break;
- case PatternCursor::instrColumn:
- dest->instr = src.instr;
- break;
- case PatternCursor::volumeColumn:
- dest->vol = src.vol;
- dest->volcmd = src.volcmd;
- break;
- case PatternCursor::effectColumn:
- dest->command = src.command;
- break;
- case PatternCursor::paramColumn:
- dest->param = src.param;
- break;
- }
- }
- }
- }
- // Adjust selection
- m_Selection = PatternRect(startSel, PatternCursor(std::min(finalDest, static_cast<ROWINDEX>(pSndFile->Patterns[m_nPattern].GetNumRows() - 1)), endSel));
- InvalidatePattern();
- SetModified();
- EndWaitCursor();
- SetFocus();
- }
- void CViewPattern::OnClearSelectionFromMenu()
- {
- OnClearSelection();
- }
- void CViewPattern::OnClearSelection(bool ITStyle, RowMask rm) //Default RowMask: all elements enabled
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled_bmsg())
- {
- return;
- }
- BeginWaitCursor();
- // If selection ends to a note column, in ITStyle extending it to instrument column since the instrument data is
- // removed with note data.
- if(ITStyle && m_Selection.GetEndColumn() == PatternCursor::noteColumn)
- {
- PatternCursor lower(m_Selection.GetLowerRight());
- lower.Move(0, 0, 1);
- m_Selection = PatternRect(m_Selection.GetUpperLeft(), lower);
- }
- m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
- PrepareUndo(m_Selection, "Clear Selection");
- ApplyToSelection([&] (ModCommand &m, ROWINDEX row, CHANNELINDEX chn)
- {
- for(int i = PatternCursor::firstColumn; i <= PatternCursor::lastColumn; i++)
- {
- PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(i));
- if(!m_Selection.ContainsHorizontal(cell))
- {
- // We might have to skip the first / last few entries.
- continue;
- }
- switch(i)
- {
- case PatternCursor::noteColumn: // Clear note
- if(rm.note)
- {
- if(m.IsPcNote())
- { // Clear whole cell if clearing PC note
- m.Clear();
- } else
- {
- m.note = NOTE_NONE;
- if(ITStyle)
- m.instr = 0;
- }
- }
- break;
- case PatternCursor::instrColumn: // Clear instrument
- if(rm.instrument)
- {
- m.instr = 0;
- }
- break;
- case PatternCursor::volumeColumn: // Clear volume
- if(rm.volume)
- {
- m.volcmd = VOLCMD_NONE;
- m.vol = 0;
- }
- break;
- case PatternCursor::effectColumn: // Clear Command
- if(rm.command)
- {
- m.command = CMD_NONE;
- if(m.IsPcNote())
- {
- m.SetValueEffectCol(0);
- }
- }
- break;
- case PatternCursor::paramColumn: // Clear Command Param
- if(rm.parameter)
- {
- m.param = 0;
- if(m.IsPcNote())
- {
- m.SetValueEffectCol(0);
- if(cell.CompareColumn(m_Selection.GetUpperLeft()) == 0)
- {
- // If this is the first selected column, update effect column char as well
- PatternCursor upper(m_Selection.GetUpperLeft());
- upper.Move(0, 0, -1);
- m_Selection = PatternRect(upper, m_Selection.GetLowerRight());
- }
- }
- }
- break;
- }
- }
- });
- // Expand invalidation to the whole column. Needed for:
- // - Last column is the effect character (parameter needs to be invalidated, too
- // - PC Notes
- // - Default volume display is enabled.
- PatternCursor endCursor(m_Selection.GetEndRow(), m_Selection.GetEndChannel() + 1);
- InvalidateArea(m_Selection.GetUpperLeft(), endCursor);
- SetModified();
- EndWaitCursor();
- SetFocus();
- }
- void CViewPattern::OnEditCut()
- {
- OnEditCopy();
- OnClearSelection(false);
- }
- void CViewPattern::OnEditCopy()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- CopyPattern(m_nPattern, m_Selection);
- SetFocus();
- }
- }
- void CViewPattern::StartRecordGroupDragging(const DragItem source)
- {
- // Drag-select record channels
- const auto *modDoc = GetDocument();
- if(modDoc == nullptr)
- return;
- m_initialDragRecordStatus.resize(modDoc->GetNumChannels());
- for(CHANNELINDEX chn = 0; chn < modDoc->GetNumChannels(); chn++)
- {
- m_initialDragRecordStatus[chn] = modDoc->GetChannelRecordGroup(chn);
- }
- m_Status.reset(psDragging);
- m_nDropItem = m_nDragItem = source;
- }
- void CViewPattern::OnLButtonDown(UINT nFlags, CPoint point)
- {
- const auto *modDoc = GetDocument();
- if(modDoc == nullptr)
- return;
- const auto &sndFile = modDoc->GetSoundFile();
- SetFocus();
- m_nDropItem = m_nDragItem = GetDragItem(point, m_rcDragItem);
- m_Status.set(psDragging);
- m_bInItemRect = true;
- m_Status.reset(psShiftDragging);
- PatternCursor pointCursor(GetPositionFromPoint(point));
- SetCapture();
- if(point.x >= m_szHeader.cx && point.y <= m_szHeader.cy - m_szPluginHeader.cy)
- {
- // Click on channel header
- if(nFlags & MK_CONTROL)
- TogglePendingMute(pointCursor.GetChannel());
- if(nFlags & MK_SHIFT)
- {
- // Drag-select record channels
- StartRecordGroupDragging(m_nDragItem);
- }
- } else if(point.x >= m_szHeader.cx && point.y > m_szHeader.cy)
- {
- // Click on pattern data
- if(IsLiveRecord() && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOFOLLOWONCLICK))
- {
- SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 0);
- }
- if(CMainFrame::GetInputHandler()->SelectionPressed()
- && (m_Status[psShiftSelect]
- || m_Selection.GetUpperLeft() == m_Selection.GetLowerRight()
- || !m_Selection.Contains(pointCursor)))
- {
- // Shift pressed -> set 2nd selection point
- // This behaviour is only used if:
- // * Shift-click has previously been used since the shift key has been pressed down (psShiftSelect flag is set),
- // * No selection has been made yet, or
- // * Shift-clicking outside the current selection.
- // This is necessary so that selections can still be moved properly while the shift button is pressed (for copy-move).
- DragToSel(pointCursor, true, true);
- m_Status.set(psShiftSelect);
- } else
- {
- // Set first selection point
- m_StartSel = pointCursor;
- if(m_StartSel.GetChannel() < sndFile.GetNumChannels())
- {
- m_Status.set(psMouseDragSelect);
- if(m_Status[psCtrlDragSelect])
- {
- SetCurSel(m_StartSel);
- }
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_DRAGNDROPEDIT)
- && ((m_Selection.GetUpperLeft() != m_Selection.GetLowerRight()) || m_Status[psCtrlDragSelect])
- && m_Selection.Contains(m_StartSel))
- {
- m_Status.set(psDragnDropEdit);
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CENTERROW)
- {
- SetCurSel(m_StartSel);
- } else
- {
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(m_StartSel);
- }
- }
- }
- } else if(point.x < m_szHeader.cx && point.y > m_szHeader.cy)
- {
- // Mark row number => mark whole row (start)
- InvalidateSelection();
- if(pointCursor.GetRow() < sndFile.Patterns[m_nPattern].GetNumRows())
- {
- m_StartSel.Set(pointCursor.GetRow(), 0);
- SetCurSel(m_StartSel, PatternCursor(pointCursor.GetRow(), sndFile.GetNumChannels() - 1, PatternCursor::lastColumn));
- m_Status.set(psRowSelection);
- }
- }
- if(m_nDragItem.IsValid())
- {
- InvalidateRect(&m_rcDragItem, FALSE);
- UpdateWindow();
- }
- }
- void CViewPattern::OnLButtonDblClk(UINT uFlags, CPoint point)
- {
- PatternCursor cursor = GetPositionFromPoint(point);
- if(cursor == m_Cursor && point.y >= m_szHeader.cy)
- {
- // Double-click pattern cell: Select whole column or show cell properties.
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_DBLCLICKSELECT))
- {
- OnSelectCurrentChannel();
- m_Status.set(psChannelSelection | psDragging);
- return;
- } else
- {
- if(ShowEditWindow())
- return;
- }
- }
- OnLButtonDown(uFlags, point);
- }
- void CViewPattern::OnLButtonUp(UINT nFlags, CPoint point)
- {
- CModDoc *modDoc = GetDocument();
- if(modDoc == nullptr)
- return;
- const auto dragType = m_nDragItem.Type();
- const bool wasDraggingRecordGroup = IsDraggingRecordGroup();
- const bool itemSelected = m_bInItemRect || (dragType == DragItem::ChannelHeader);
- m_bInItemRect = false;
- ResetRecordGroupDragging();
- ReleaseCapture();
- m_Status.reset(psMouseDragSelect | psRowSelection | psChannelSelection | psDragging);
- // Drag & Drop Editing
- if(m_Status[psDragnDropEdit])
- {
- if(m_Status[psDragnDropping])
- {
- OnDrawDragSel();
- m_Status.reset(psDragnDropping);
- OnDropSelection();
- }
- if(GetPositionFromPoint(point) == m_StartSel)
- {
- SetCursorPosition(m_StartSel);
- }
- SetCursor(CMainFrame::curArrow);
- m_Status.reset(psDragnDropEdit);
- }
- if(dragType != DragItem::ChannelHeader
- && dragType != DragItem::PatternHeader
- && dragType != DragItem::PluginName)
- {
- if((m_nMidRow) && (m_Selection.GetUpperLeft() == m_Selection.GetLowerRight()))
- {
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(m_Selection.GetUpperLeft());
- //UpdateIndicator();
- }
- }
- if(!itemSelected || !m_nDragItem.IsValid())
- return;
- InvalidateRect(&m_rcDragItem, FALSE);
- const CHANNELINDEX sourceChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
- const CHANNELINDEX targetChn = m_nDropItem.IsValid() ? static_cast<CHANNELINDEX>(m_nDropItem.Value()) : CHANNELINDEX_INVALID;
- switch(m_nDragItem.Type())
- {
- case DragItem::ChannelHeader:
- if(sourceChn == targetChn && targetChn < modDoc->GetNumChannels())
- {
- // Just clicked a channel header...
- if(nFlags & MK_SHIFT)
- {
- // Toggle record state
- modDoc->ToggleChannelRecordGroup(sourceChn, RecordGroup::Group1);
- InvalidateChannelsHeaders(sourceChn);
- } else if(CMainFrame::GetInputHandler()->AltPressed())
- {
- // Solo / Unsolo
- OnSoloChannel(sourceChn);
- } else if(!(nFlags & MK_CONTROL))
- {
- // Mute / Unmute
- OnMuteChannel(sourceChn);
- }
- } else if(!wasDraggingRecordGroup && targetChn < modDoc->GetNumChannels() && m_nDropItem.Type() == DragItem::ChannelHeader)
- {
- // Dragged to other channel header => move or copy channel
- InvalidateRect(&m_rcDropItem, FALSE);
- const bool duplicate = (nFlags & MK_SHIFT) != 0;
- DragChannel(sourceChn, targetChn, 1, duplicate);
- }
- break;
- case DragItem::PatternHeader:
- OnPatternProperties();
- break;
- case DragItem::PluginName:
- if(sourceChn < MAX_BASECHANNELS)
- TogglePluginEditor(sourceChn);
- break;
- }
- m_nDropItem = {};
- }
- void CViewPattern::DragChannel(CHANNELINDEX source, CHANNELINDEX target, CHANNELINDEX numChannels, bool duplicate)
- {
- auto modDoc = GetDocument();
- const CHANNELINDEX newChannels = modDoc->GetNumChannels() + (duplicate ? numChannels : 0);
- std::vector<CHANNELINDEX> channels(newChannels, 0);
- bool modified = duplicate;
- for(CHANNELINDEX chn = 0, fromChn = 0; chn < newChannels; chn++)
- {
- if(chn >= target && chn < target + numChannels)
- {
- channels[chn] = source + chn - target;
- } else
- {
- if(fromChn == source && !duplicate) // Don't want the source channels twice if we're just moving
- {
- fromChn += numChannels;
- }
- channels[chn] = fromChn++;
- }
- if(channels[chn] != chn)
- {
- modified = true;
- }
- }
- if(modified && modDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
- {
- modDoc->UpdateAllViews(this, GeneralHint().Channels().ModType(), this);
- if(duplicate)
- {
- // Number of channels changed: Update channel headers and other information.
- SetCurrentPattern(m_nPattern);
- }
- if(!duplicate)
- {
- const auto oldSel = m_Selection;
- if(auto chn = m_Cursor.GetChannel(); (chn >= source && chn < source + numChannels))
- SetCurrentColumn(target + chn - source, m_Cursor.GetColumnType());
- if(oldSel.GetStartChannel() >= source && oldSel.GetEndChannel() < source + numChannels)
- {
- const auto diff = static_cast<int>(target) - source;
- auto upperLeft = oldSel.GetUpperLeft(), lowerRight = oldSel.GetLowerRight();
- upperLeft.Move(0, diff, 0);
- lowerRight.Move(0, diff, 0);
- SetCurSel(upperLeft, lowerRight);
- }
- }
- InvalidatePattern(true, false);
- SetModified(false);
- }
- }
- void CViewPattern::ShowPatternProperties(PATTERNINDEX pat)
- {
- CModDoc *pModDoc = GetDocument();
- if(pat == PATTERNINDEX_INVALID)
- pat = m_nPattern;
- if(pModDoc && pModDoc->GetSoundFile().Patterns.IsValidPat(pat))
- {
- CPatternPropertiesDlg dlg(*pModDoc, pat, this);
- if(dlg.DoModal() == IDOK)
- {
- UpdateScrollSize();
- InvalidatePattern(true, true);
- SanitizeCursor();
- pModDoc->UpdateAllViews(this, PatternHint(pat).Data(), this);
- }
- }
- }
- void CViewPattern::OnRButtonDown(UINT flags, CPoint pt)
- {
- CModDoc *modDoc = GetDocument();
- HMENU hMenu;
- // Too far left to get a ctx menu:
- if(!modDoc || pt.x < m_szHeader.cx)
- {
- return;
- }
- // Handle drag n drop
- if(m_Status[psDragnDropEdit])
- {
- if(m_Status[psDragnDropping])
- {
- OnDrawDragSel();
- m_Status.reset(psDragnDropping);
- }
- m_Status.reset(psDragnDropEdit | psMouseDragSelect);
- if(m_Status[psDragging])
- {
- m_Status.reset(psDragging);
- m_bInItemRect = false;
- ReleaseCapture();
- }
- SetCursor(CMainFrame::curArrow);
- return;
- }
- if((hMenu = ::CreatePopupMenu()) == NULL)
- {
- return;
- }
- CSoundFile &sndFile = modDoc->GetSoundFile();
- m_MenuCursor = GetPositionFromPoint(pt);
- // Right-click outside single-point selection? Reposition cursor to the new location
- if(!m_Selection.Contains(m_MenuCursor) && m_Selection.GetUpperLeft() == m_Selection.GetLowerRight())
- {
- if(pt.y > m_szHeader.cy)
- {
- //ensure we're not clicking header
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(m_MenuCursor);
- }
- }
- const CHANNELINDEX nChn = m_MenuCursor.GetChannel();
- const bool inChannelHeader = (pt.y < m_szHeader.cy);
- if((flags & MK_CONTROL) && nChn < sndFile.GetNumChannels() && inChannelHeader)
- {
- // Ctrl+Right-Click: Open quick channel properties.
- ClientToScreen(&pt);
- m_quickChannelProperties.Show(GetDocument(), nChn, pt);
- } else if((flags & MK_SHIFT) && inChannelHeader)
- {
- // Drag-select record channels
- StartRecordGroupDragging(GetDragItem(pt, m_rcDragItem));
- } else if(nChn < sndFile.GetNumChannels() && sndFile.Patterns.IsValidPat(m_nPattern) && !(flags & (MK_CONTROL | MK_SHIFT)))
- {
- CInputHandler *ih = CMainFrame::GetInputHandler();
- //------ Plugin Header Menu --------- :
- if(m_Status[psShowPluginNames] &&
- inChannelHeader && (pt.y > m_szHeader.cy - m_szPluginHeader.cy))
- {
- BuildPluginCtxMenu(hMenu, nChn, sndFile);
- }
- //------ Channel Header Menu ---------- :
- else if(inChannelHeader)
- {
- if(ih->ShiftPressed())
- {
- //Don't bring up menu if shift is pressed, else we won't get button up msg.
- } else
- {
- if(BuildSoloMuteCtxMenu(hMenu, ih, nChn, sndFile))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- BuildRecordCtxMenu(hMenu, ih, nChn);
- BuildChannelControlCtxMenu(hMenu, ih);
- }
- }
- //------ Standard Menu ---------- :
- else if((pt.x >= m_szHeader.cx) && (pt.y >= m_szHeader.cy))
- {
- // When combining menus, use bitwise ORs to avoid shortcuts
- if(BuildSelectionCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildEditCtxMenu(hMenu, ih, modDoc))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildInterpolationCtxMenu(hMenu, ih)
- | BuildTransposeCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildVisFXCtxMenu(hMenu, ih)
- | BuildAmplifyCtxMenu(hMenu, ih)
- | BuildSetInstCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildPCNoteCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildGrowShrinkCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildMiscCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- if(BuildRowInsDelCtxMenu(hMenu, ih))
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- CString s = _T("&Quantize ");
- if(TrackerSettings::Instance().recordQuantizeRows != 0)
- {
- uint32 rows = TrackerSettings::Instance().recordQuantizeRows.Get();
- s += MPT_CFORMAT("(Currently: {} Row{})")(rows, CString(rows == 1 ? _T("") : _T("s")));
- } else
- {
- s += _T("Settings...");
- }
- AppendMenu(hMenu, MF_STRING | (TrackerSettings::Instance().recordQuantizeRows != 0 ? MF_CHECKED : 0), ID_SETQUANTIZE, ih->GetKeyTextFromCommand(kcQuantizeSettings, s));
- }
- ClientToScreen(&pt);
- ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
- } else if(nChn >= sndFile.GetNumChannels() && sndFile.GetNumChannels() < sndFile.GetModSpecifications().channelsMax && !(flags & (MK_CONTROL | MK_SHIFT)))
- {
- // Click outside of pattern: Offer easy way to add more channels
- m_MenuCursor.Set(0, sndFile.GetNumChannels() - 1);
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_AFTER, _T("&Add Channel"));
- ClientToScreen(&pt);
- ::TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
- }
- ::DestroyMenu(hMenu);
- }
- void CViewPattern::OnRButtonUp(UINT nFlags, CPoint point)
- {
- CModDoc *pModDoc = GetDocument();
- if(!pModDoc)
- return;
- ResetRecordGroupDragging();
- const CHANNELINDEX sourceChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
- const CHANNELINDEX targetChn = m_nDropItem.IsValid() ? static_cast<CHANNELINDEX>(m_nDropItem.Value()) : CHANNELINDEX_INVALID;
- switch(m_nDragItem.Type())
- {
- case DragItem::ChannelHeader:
- if(nFlags & MK_SHIFT)
- {
- if(sourceChn < MAX_BASECHANNELS && sourceChn == targetChn)
- {
- pModDoc->ToggleChannelRecordGroup(sourceChn, RecordGroup::Group2);
- InvalidateChannelsHeaders(sourceChn);
- }
- }
- break;
- }
- CModScrollView::OnRButtonUp(nFlags, point);
- }
- BOOL CViewPattern::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
- {
- if(nFlags & MK_CONTROL)
- {
- // Ctrl + mouse wheel: Increment / decrement values
- DataEntry(zDelta > 0, (nFlags & MK_SHIFT) == MK_SHIFT);
- return TRUE;
- }
- if(IsLiveRecord() && !m_Status[psDragActive])
- {
- // During live playback with "follow song" enabled, the mouse wheel can be used to jump forwards and backwards.
- CursorJump(-mpt::signum(zDelta), false);
- return TRUE;
- }
- return CModScrollView::OnMouseWheel(nFlags, zDelta, pt);
- }
- void CViewPattern::OnXButtonUp(UINT nFlags, UINT nButton, CPoint point)
- {
- if(nButton == XBUTTON1)
- OnPrevOrder();
- else if(nButton == XBUTTON2)
- OnNextOrder();
- CModScrollView::OnXButtonUp(nFlags, nButton, point);
- }
- void CViewPattern::OnMouseMove(UINT nFlags, CPoint point)
- {
- CModScrollView::OnMouseMove(nFlags, point);
- const bool isDraggingRecordGroup = IsDraggingRecordGroup();
- if(!m_Status[psDragging] && !isDraggingRecordGroup)
- return;
- // Drag&Drop actions
- if(m_nDragItem.IsValid())
- {
- const CRect oldDropRect = m_rcDropItem;
- const auto oldDropItem = m_nDropItem;
- if(isDraggingRecordGroup)
- {
- // When drag-selecting record channels, ignore y position
- point.y = m_rcDragItem.top;
- }
- m_Status.set(psShiftDragging, (nFlags & MK_SHIFT) != 0);
- m_nDropItem = GetDragItem(point, m_rcDropItem);
- const bool b = (m_nDropItem == m_nDragItem);
- const bool dragChannel = m_nDragItem.Type() == DragItem::ChannelHeader;
- if(b != m_bInItemRect || (m_nDropItem != oldDropItem && dragChannel))
- {
- m_bInItemRect = b;
- InvalidateRect(&m_rcDragItem, FALSE);
- // Drag-select record channels
- if(isDraggingRecordGroup && m_nDropItem.Type() == DragItem::ChannelHeader)
- {
- auto modDoc = GetDocument();
- auto startChn = static_cast<CHANNELINDEX>(m_nDragItem.Value());
- auto endChn = static_cast<CHANNELINDEX>(m_nDropItem.Value());
- RecordGroup setRecord = RecordGroup::NoGroup;
- if(m_initialDragRecordStatus[startChn] != RecordGroup::Group1 && (nFlags & MK_LBUTTON))
- setRecord = RecordGroup::Group1;
- else if (m_initialDragRecordStatus[startChn] != RecordGroup::Group2 && (nFlags & MK_RBUTTON))
- setRecord = RecordGroup::Group2;
- if(startChn > endChn)
- std::swap(startChn, endChn);
- CHANNELINDEX numChannels = std::min(modDoc->GetNumChannels(), static_cast<CHANNELINDEX>(m_initialDragRecordStatus.size()));
- for(CHANNELINDEX chn = 0; chn < numChannels; chn++)
- {
- auto oldState = modDoc->GetChannelRecordGroup(chn);
- if(chn >= startChn && chn <= endChn)
- GetDocument()->SetChannelRecordGroup(chn, setRecord);
- else
- GetDocument()->SetChannelRecordGroup(chn, m_initialDragRecordStatus[chn]);
- if(oldState != modDoc->GetChannelRecordGroup(chn))
- InvalidateChannelsHeaders(chn);
- }
- } else
- {
- // Dragging around channel headers? Update move indicator...
- if(m_nDropItem.Type() == DragItem::ChannelHeader)
- InvalidateRect(&m_rcDropItem, FALSE);
- if(oldDropItem.Type() == DragItem::ChannelHeader)
- InvalidateRect(&oldDropRect, FALSE);
- }
- UpdateWindow();
- }
- }
- if(m_Status[psChannelSelection])
- {
- // Double-clicked a pattern cell to select whole channel.
- // Continue dragging to select more channels.
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- const ROWINDEX lastRow = pSndFile->Patterns[m_nPattern].GetNumRows() - 1;
- CHANNELINDEX startChannel = m_Cursor.GetChannel();
- CHANNELINDEX endChannel = GetPositionFromPoint(point).GetChannel();
- m_StartSel = PatternCursor(0, startChannel, (startChannel <= endChannel ? PatternCursor::firstColumn : PatternCursor::lastColumn));
- PatternCursor endSel = PatternCursor(lastRow, endChannel, (startChannel <= endChannel ? PatternCursor::lastColumn : PatternCursor::firstColumn));
- DragToSel(endSel, true, false, false);
- }
- } else if(m_Status[psRowSelection] && point.y > m_szHeader.cy)
- {
- // Mark row number => mark whole row (continue)
- InvalidateSelection();
- PatternCursor cursor(GetPositionFromPoint(point));
- cursor.SetColumn(GetDocument()->GetNumChannels() - 1, PatternCursor::lastColumn);
- DragToSel(cursor, false, true, false);
- } else if(m_Status[psMouseDragSelect])
- {
- PatternCursor cursor(GetPositionFromPoint(point));
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && m_nPattern < pSndFile->Patterns.Size())
- {
- ROWINDEX row = cursor.GetRow();
- LimitMax(row, pSndFile->Patterns[m_nPattern].GetNumRows() - 1);
- cursor.SetRow(row);
- }
- // Drag & Drop editing
- if(m_Status[psDragnDropEdit])
- {
- const bool moved = m_DragPos.GetChannel() != cursor.GetChannel() || m_DragPos.GetRow() != cursor.GetRow();
- if(!m_Status[psDragnDropping])
- {
- SetCursor(CMainFrame::curDragging);
- }
- if(!m_Status[psDragnDropping] || moved)
- {
- if(m_Status[psDragnDropping])
- OnDrawDragSel();
- m_Status.reset(psDragnDropping);
- DragToSel(cursor, true, true, true);
- m_DragPos = cursor;
- m_Status.set(psDragnDropping);
- OnDrawDragSel();
- }
- } else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CENTERROW)
- {
- // Default: selection
- DragToSel(cursor, true, true);
- } else
- {
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(cursor);
- }
- }
- }
- void CViewPattern::OnEditSelectAll()
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- SetCurSel(PatternCursor(0), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn));
- }
- }
- void CViewPattern::OnEditSelectChannel()
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- SetCurSel(PatternCursor(0, m_MenuCursor.GetChannel()), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, m_MenuCursor.GetChannel(), PatternCursor::lastColumn));
- }
- }
- void CViewPattern::OnSelectCurrentChannel()
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- PatternCursor beginSel(0, GetCurrentChannel());
- PatternCursor endSel(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, GetCurrentChannel(), PatternCursor::lastColumn);
- // If column is already selected, select the current pattern
- if((beginSel == m_Selection.GetUpperLeft()) && (endSel == m_Selection.GetLowerRight()))
- {
- beginSel.Set(0, 0);
- endSel.Set(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn);
- }
- SetCurSel(beginSel, endSel);
- }
- }
- void CViewPattern::OnSelectCurrentColumn()
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- SetCurSel(PatternCursor(0, m_Cursor), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, m_Cursor));
- }
- }
- void CViewPattern::OnChannelReset()
- {
- ResetChannel(m_MenuCursor.GetChannel());
- }
- // Reset all channel variables
- void CViewPattern::ResetChannel(CHANNELINDEX chn)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- CriticalSection cs;
- if(!pModDoc->IsChannelMuted(chn))
- {
- // Cut playing notes
- sndFile.ChnSettings[chn].dwFlags.set(CHN_MUTE);
- pModDoc->UpdateChannelMuteStatus(chn);
- sndFile.ChnSettings[chn].dwFlags.reset(CHN_MUTE);
- }
- sndFile.m_PlayState.Chn[chn].Reset(ModChannel::resetTotal, sndFile, chn, CSoundFile::GetChannelMuteFlag());
- }
- void CViewPattern::OnMuteFromClick()
- {
- OnMuteChannel(m_MenuCursor.GetChannel());
- }
- void CViewPattern::OnMuteChannel(CHANNELINDEX chn)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- pModDoc->SoloChannel(chn, false);
- pModDoc->MuteChannel(chn, !pModDoc->IsChannelMuted(chn));
- //If we just unmuted a channel, make sure none are still considered "solo".
- if(!pModDoc->IsChannelMuted(chn))
- {
- for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++)
- {
- pModDoc->SoloChannel(i, false);
- }
- }
- InvalidateChannelsHeaders();
- pModDoc->UpdateAllViews(this, GeneralHint(chn).Channels());
- }
- }
- void CViewPattern::OnSoloFromClick()
- {
- OnSoloChannel(m_MenuCursor.GetChannel());
- }
- // When trying to solo a channel that is already the only unmuted channel,
- // this will result in unmuting all channels, in order to satisfy user habits.
- // In all other cases, soloing a channel unsoloes all and mutes all except this channel
- void CViewPattern::OnSoloChannel(CHANNELINDEX chn)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- if(chn >= pModDoc->GetNumChannels())
- {
- return;
- }
- if(pModDoc->IsChannelSolo(chn))
- {
- bool nChnIsOnlyUnMutedChan = true;
- for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++) //check status of all other chans
- {
- if(i != chn && !pModDoc->IsChannelMuted(i))
- {
- nChnIsOnlyUnMutedChan = false; //found a channel that isn't muted!
- break;
- }
- }
- if(nChnIsOnlyUnMutedChan) // this is the only playable channel and it is already soloed -> Unmute all
- {
- OnUnmuteAll();
- return;
- }
- }
- for(CHANNELINDEX i = 0; i < pModDoc->GetNumChannels(); i++)
- {
- pModDoc->MuteChannel(i, !(i == chn)); //mute all chans except nChn, unmute nChn
- pModDoc->SoloChannel(i, (i == chn)); //unsolo all chans except nChn, solo nChn
- }
- InvalidateChannelsHeaders();
- pModDoc->UpdateAllViews(this, GeneralHint(chn).Channels());
- }
- void CViewPattern::OnRecordSelect()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- CHANNELINDEX chn = m_MenuCursor.GetChannel();
- if(chn < pModDoc->GetNumChannels())
- {
- pModDoc->ToggleChannelRecordGroup(chn, RecordGroup::Group1);
- InvalidateChannelsHeaders(chn);
- }
- }
- }
- void CViewPattern::OnSplitRecordSelect()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- CHANNELINDEX chn = m_MenuCursor.GetChannel();
- if(chn < pModDoc->GetNumChannels())
- {
- pModDoc->ToggleChannelRecordGroup(chn, RecordGroup::Group2);
- InvalidateChannelsHeaders(chn);
- }
- }
- }
- void CViewPattern::OnUnmuteAll()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc)
- {
- const CHANNELINDEX numChannels = pModDoc->GetNumChannels();
- for(CHANNELINDEX chn = 0; chn < numChannels; chn++)
- {
- pModDoc->MuteChannel(chn, false);
- pModDoc->SoloChannel(chn, false);
- }
- InvalidateChannelsHeaders();
- }
- }
- bool CViewPattern::InsertOrDeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit, bool deleteRows)
- {
- CModDoc &modDoc = *GetDocument();
- CSoundFile &sndFile = *GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled_bmsg())
- return false;
- LimitMax(lastChn, CHANNELINDEX(sndFile.GetNumChannels() - 1));
- if(firstChn > lastChn)
- return false;
- const auto selection = (firstChn != lastChn || m_Selection.GetNumRows() > 1) ? PatternRect{{m_Selection.GetStartRow(), firstChn, PatternCursor::firstColumn}, {m_Selection.GetEndRow(), lastChn, PatternCursor::lastColumn}} : m_Selection;
- const ROWINDEX numRows = selection.GetNumRows();
- const char *undoDescription = "";
- if(deleteRows)
- undoDescription = numRows != 1 ? "Delete Rows" : "Delete Row";
- else
- undoDescription = numRows != 1 ? "Insert Rows" : "Insert Row";
- const ROWINDEX startRow = selection.GetStartRow();
- const CHANNELINDEX numChannels = lastChn - firstChn + 1;
- std::vector<PATTERNINDEX> patterns;
- if(globalEdit)
- {
- auto &order = Order();
- const auto start = order.begin() + GetCurrentOrder();
- const auto end = std::find(start, order.end(), order.GetInvalidPatIndex());
- // As this is a global operation, ensure that all modified patterns are unique
- bool orderListChanged = false;
- const ORDERINDEX ordEnd = GetCurrentOrder() + static_cast<ORDERINDEX>(std::distance(start, end));
- for(ORDERINDEX ord = GetCurrentOrder(); ord < ordEnd; ord++)
- {
- const auto pat = order[ord];
- if(pat != order.EnsureUnique(ord))
- orderListChanged = true;
- }
- if(orderListChanged)
- modDoc.UpdateAllViews(this, SequenceHint().Data(), nullptr);
- patterns.assign(start, end);
- } else
- {
- patterns = {m_nPattern};
- }
- // Backup source data and create undo points
- std::vector<ModCommand> patternData;
- if(!deleteRows)
- patternData.insert(patternData.begin(), numRows * numChannels, ModCommand{});
- bool first = true;
- for(auto pat : patterns)
- {
- if(!sndFile.Patterns.IsValidPat(pat))
- continue;
- const auto &pattern = sndFile.Patterns[pat];
- const ROWINDEX firstRow = first ? startRow : 0;
- for(ROWINDEX row = firstRow; row < pattern.GetNumRows(); row++)
- {
- const auto *m = pattern.GetpModCommand(row, firstChn);
- patternData.insert(patternData.end(), m, m + numChannels);
- }
- modDoc.GetPatternUndo().PrepareUndo(pat, firstChn, firstRow, numChannels, pattern.GetNumRows(), undoDescription, !first);
- first = false;
- }
- if(deleteRows)
- patternData.insert(patternData.end(), numRows * numChannels, ModCommand{});
- // Now do the actual shifting
- auto src = patternData.cbegin();
- if(deleteRows)
- src += numRows * numChannels;
- PATTERNINDEX firstNewPattern = m_nPattern;
- first = true;
- for(auto pat : patterns)
- {
- if(!sndFile.Patterns.IsValidPat(pat))
- continue;
- auto &pattern = sndFile.Patterns[pat];
- for(ROWINDEX row = first ? startRow : 0; row < pattern.GetNumRows(); row++, src += numChannels)
- {
- ModCommand *dest = pattern.GetpModCommand(row, firstChn);
- std::copy(src, src + numChannels, dest);
- }
- if(first)
- firstNewPattern = pat;
- first = false;
- modDoc.UpdateAllViews(this, PatternHint(pat).Data(), this);
- }
- SetModified();
- SetCurrentPattern(firstNewPattern);
- InvalidatePattern();
- SetCursorPosition(selection.GetUpperLeft());
- SetCurSel(selection);
- return true;
- }
- void CViewPattern::DeleteRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit)
- {
- InsertOrDeleteRows(firstChn, lastChn, globalEdit, true);
- }
- void CViewPattern::OnDeleteRow()
- {
- DeleteRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel());
- }
- void CViewPattern::OnDeleteWholeRow()
- {
- DeleteRows(0, GetSoundFile()->GetNumChannels() - 1);
- }
- void CViewPattern::OnDeleteRowGlobal()
- {
- DeleteRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel(), true);
- }
- void CViewPattern::OnDeleteWholeRowGlobal()
- {
- DeleteRows(0, GetSoundFile()->GetNumChannels() - 1, true);
- }
- void CViewPattern::InsertRows(CHANNELINDEX firstChn, CHANNELINDEX lastChn, bool globalEdit)
- {
- InsertOrDeleteRows(firstChn, lastChn, globalEdit, false);
- }
- void CViewPattern::OnInsertRow()
- {
- InsertRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel());
- }
- void CViewPattern::OnInsertWholeRow()
- {
- InsertRows(0, GetSoundFile()->GetNumChannels() - 1);
- }
- void CViewPattern::OnInsertRowGlobal()
- {
- InsertRows(m_Selection.GetStartChannel(), m_Selection.GetEndChannel(), true);
- }
- void CViewPattern::OnInsertWholeRowGlobal()
- {
- InsertRows(0, GetSoundFile()->GetNumChannels() - 1, true);
- }
- void CViewPattern::OnSplitPattern()
- {
- COrderList &orderList = static_cast<CCtrlPatterns *>(GetControlDlg())->GetOrderList();
- CSoundFile &sndFile = *GetSoundFile();
- const auto &specs = sndFile.GetModSpecifications();
- const PATTERNINDEX sourcePat = m_nPattern;
- const ROWINDEX splitRow = m_MenuCursor.GetRow();
- if(splitRow < 1 || !sndFile.Patterns.IsValidPat(sourcePat) || !sndFile.Patterns[sourcePat].IsValidRow(splitRow))
- {
- MessageBeep(MB_ICONWARNING);
- return;
- }
- // 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)
- CriticalSection cs;
- const ROWINDEX numSplitRows = sndFile.Patterns[sourcePat].GetNumRows() - splitRow;
- const PATTERNINDEX newPat = sndFile.Patterns.InsertAny(std::max(specs.patternRowsMin, numSplitRows), false);
- if(newPat == PATTERNINDEX_INVALID)
- {
- cs.Leave();
- Reporting::Error(MPT_AFORMAT("Pattern limit of the {} format ({} patterns) has been reached.")(mpt::ToUpperCaseAscii(specs.fileExtension), specs.patternsMax), "Split Pattern");
- return;
- }
- auto &sourcePattern = sndFile.Patterns[sourcePat];
- auto &newPattern = sndFile.Patterns[newPat];
- auto &undo = GetDocument()->GetPatternUndo();
- undo.PrepareUndo(sourcePat, 0, splitRow, sourcePattern.GetNumChannels(), numSplitRows, "Split Pattern");
- undo.PrepareUndo(newPat, 0, 0, newPattern.GetNumChannels(), newPattern.GetNumRows(), "Split Pattern", true);
- auto copyStart = sourcePattern.begin() + sourcePattern.GetNumChannels() * splitRow;
- std::copy(copyStart, sourcePattern.end(), newPattern.begin());
- // Reduce the row number or insert pattern breaks, if the patterns are too small for the format
- sourcePattern.Resize(std::max(specs.patternRowsMin, splitRow));
- if(splitRow != sourcePattern.GetNumRows())
- {
- std::fill(copyStart, sourcePattern.end(), ModCommand::Empty());
- sourcePattern.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(splitRow - 1).RetryNextRow());
- }
- if(numSplitRows != newPattern.GetNumRows())
- {
- newPattern.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(numSplitRows - 1).RetryNextRow());
- }
- // Update every occurrence of the split pattern in all order lists
- auto editOrd = GetCurrentOrder();
- for(SEQUENCEINDEX seq = 0; seq < sndFile.Order.GetNumSequences(); seq++)
- {
- const bool isCurrentSeq = (seq == sndFile.Order.GetCurrentSequenceIndex());
- bool editedSeq = false;
- auto &order = sndFile.Order(seq);
- for(ORDERINDEX i = 0; i < order.GetLength(); i++)
- {
- if(order[i] == sourcePat)
- {
- if(!order.insert(i + 1, 1, newPat))
- continue;
- editedSeq = true;
- if(isCurrentSeq)
- orderList.InsertUpdatePlaystate(i, i + 1);
- i++;
- // Slide the current selection accordingly so it doesn't end up in the wrong id
- if(i < editOrd && isCurrentSeq)
- editOrd++;
- }
- }
- if(editedSeq)
- GetDocument()->UpdateAllViews(nullptr, SequenceHint(seq).Data(), this);
- }
- orderList.SetSelection(editOrd + 1);
- SetCurrentRow(0);
- SetModified(true);
- GetDocument()->UpdateAllViews(nullptr, PatternHint(newPat).Names().Data(), this);
- }
- void CViewPattern::OnEditGoto()
- {
- CModDoc *pModDoc = GetDocument();
- if(!pModDoc)
- return;
- ORDERINDEX curOrder = GetCurrentOrder();
- CHANNELINDEX curChannel = GetCurrentChannel() + 1;
- CPatternGotoDialog dlg(this, GetCurrentRow(), curChannel, m_nPattern, curOrder, pModDoc->GetSoundFile());
- if(dlg.DoModal() == IDOK)
- {
- if(dlg.m_nPattern != m_nPattern)
- SetCurrentPattern(dlg.m_nPattern);
- if(dlg.m_nOrder != curOrder)
- SetCurrentOrder(dlg.m_nOrder);
- if(dlg.m_nChannel != curChannel)
- SetCurrentColumn(dlg.m_nChannel - 1);
- if(dlg.m_nRow != GetCurrentRow())
- SetCurrentRow(dlg.m_nRow);
- CriticalSection cs;
- pModDoc->SetElapsedTime(dlg.m_nOrder, dlg.m_nRow, false);
- }
- return;
- }
- void CViewPattern::OnPatternStep()
- {
- PatternStep();
- }
- void CViewPattern::PatternStep(ROWINDEX row)
- {
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- CModDoc *pModDoc = GetDocument();
- if(pMainFrm != nullptr && pModDoc != nullptr)
- {
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- return;
- CriticalSection cs;
- // In case we were previously in smooth scrolling mode during live playback, the pattern might be misaligned.
- if(GetSmoothScrollOffset() != 0)
- InvalidatePattern(true, true);
- // Cut instruments/samples in virtual channels
- for(CHANNELINDEX i = sndFile.GetNumChannels(); i < MAX_CHANNELS; i++)
- {
- sndFile.m_PlayState.Chn[i].dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF);
- }
- sndFile.LoopPattern(m_nPattern);
- sndFile.m_PlayState.m_nNextRow = row == ROWINDEX_INVALID ? GetCurrentRow() : row;
- sndFile.m_SongFlags.reset(SONG_PAUSED);
- sndFile.m_SongFlags.set(SONG_STEP);
- SetPlayCursor(m_nPattern, sndFile.m_PlayState.m_nNextRow, 0);
- cs.Leave();
- if(pMainFrm->GetModPlaying() != pModDoc)
- {
- pModDoc->SetFollowWnd(m_hWnd);
- pMainFrm->PlayMod(pModDoc);
- }
- pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
- if(row == ROWINDEX_INVALID)
- {
- SetCurrentRow(GetCurrentRow() + 1,
- (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) || // Wrap around to next pattern if continous scroll is enabled...
- (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP)); // ...or otherwise if cursor wrap is enabled.
- }
- SetFocus();
- }
- }
- // Copy cursor to internal clipboard
- void CViewPattern::OnCursorCopy()
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- const ModCommand &m = GetCursorCommand();
- switch(m_Cursor.GetColumnType())
- {
- case PatternCursor::noteColumn:
- case PatternCursor::instrColumn:
- m_cmdOld.note = m.note;
- m_cmdOld.instr = m.instr;
- SendCtrlMessage(CTRLMSG_SETCURRENTINSTRUMENT, m_cmdOld.instr);
- break;
- case PatternCursor::volumeColumn:
- m_cmdOld.volcmd = m.volcmd;
- m_cmdOld.vol = m.vol;
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- m_cmdOld.command = m.command;
- m_cmdOld.param = m.param;
- break;
- }
- }
- // Paste cursor from internal clipboard
- void CViewPattern::OnCursorPaste()
- {
- if(!IsEditingEnabled_bmsg())
- {
- return;
- }
- PrepareUndo(m_Cursor, m_Cursor, "Cursor Paste");
- PatternCursor::Columns column = m_Cursor.GetColumnType();
- ModCommand &m = GetCursorCommand();
- switch(column)
- {
- case PatternCursor::noteColumn:
- m.note = m_cmdOld.note;
- [[fallthrough]];
- case PatternCursor::instrColumn:
- m.instr = m_cmdOld.instr;
- break;
- case PatternCursor::volumeColumn:
- m.vol = m_cmdOld.vol;
- m.volcmd = m_cmdOld.volcmd;
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- m.command = m_cmdOld.command;
- m.param = m_cmdOld.param;
- break;
- }
- SetModified(false);
- // Preview Row
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !IsLiveRecord())
- {
- PatternStep(GetCurrentRow());
- }
- if(GetSoundFile()->IsPaused() || !m_Status[psFollowSong] || (CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->GetFollowSong(GetDocument()) != m_hWnd))
- {
- InvalidateCell(m_Cursor);
- SetCurrentRow(GetCurrentRow() + m_nSpacing);
- SetSelToCursor();
- }
- }
- void CViewPattern::OnVisualizeEffect()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc != nullptr && pModDoc->GetSoundFile().Patterns.IsValidPat(m_nPattern))
- {
- const ROWINDEX row0 = m_Selection.GetStartRow(), row1 = m_Selection.GetEndRow();
- const CHANNELINDEX nchn = m_Selection.GetStartChannel();
- if(m_pEffectVis)
- {
- // Window already there, update data
- m_pEffectVis->UpdateSelection(row0, row1, nchn, m_nPattern);
- } else
- {
- // Open window & send data
- CriticalSection cs;
- try
- {
- m_pEffectVis = std::make_unique<CEffectVis>(this, row0, row1, nchn, *pModDoc, m_nPattern);
- m_pEffectVis->OpenEditor(CMainFrame::GetMainFrame());
- // HACK: to get status window set up; must create clear destinction between
- // construction, 1st draw code and all draw code.
- m_pEffectVis->OnSize(0, 0, 0);
- } catch(mpt::out_of_memory e)
- {
- mpt::delete_out_of_memory(e);
- }
- }
- }
- }
- // Helper function for sweeping the pattern up and down to find suitable start and end points for interpolation.
- // startCond must return true for the start row, endCond must return true for the end row.
- PatternRect CViewPattern::SweepPattern(bool(*startCond)(const ModCommand &), bool(*endCond)(const ModCommand &, const ModCommand &)) const
- {
- const auto &pattern = GetSoundFile()->Patterns[m_nPattern];
- const ROWINDEX numRows = pattern.GetNumRows();
- const ROWINDEX cursorRow = m_Selection.GetStartRow();
- if(cursorRow >= numRows)
- return {};
- const ModCommand *start = pattern.GetpModCommand(cursorRow, m_Selection.GetStartChannel()), *end = start;
- // Sweep up
- ROWINDEX startRow = ROWINDEX_INVALID;
- for(ROWINDEX row = 0; row <= cursorRow; row++, start -= pattern.GetNumChannels())
- {
- if(startCond(*start))
- {
- startRow = cursorRow - row;
- break;
- }
- }
- if(startRow == ROWINDEX_INVALID)
- return {};
- // Sweep down
- ROWINDEX endRow = ROWINDEX_INVALID;
- for(ROWINDEX row = cursorRow; row < numRows; row++, end += pattern.GetNumChannels())
- {
- if(endCond(*start, *end))
- {
- endRow = row;
- break;
- }
- }
-
- if(endRow == ROWINDEX_INVALID)
- return {};
-
- return {PatternCursor(startRow, m_Selection.GetUpperLeft()), PatternCursor(endRow, m_Selection.GetUpperLeft())};
- }
- void CViewPattern::Interpolate(PatternCursor::Columns type)
- {
- CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern) || !IsEditingEnabled())
- return;
- bool changed = false;
- std::vector<CHANNELINDEX> validChans;
- if(type == PatternCursor::effectColumn || type == PatternCursor::paramColumn)
- {
- std::vector<CHANNELINDEX> effectChans;
- std::vector<CHANNELINDEX> paramChans;
- ListChansWhereColSelected(PatternCursor::effectColumn, effectChans);
- ListChansWhereColSelected(PatternCursor::paramColumn, paramChans);
- validChans.resize(effectChans.size() + paramChans.size());
- validChans.resize(std::set_union(effectChans.begin(), effectChans.end(), paramChans.begin(), paramChans.end(), validChans.begin()) - validChans.begin());
- } else
- {
- ListChansWhereColSelected(type, validChans);
- }
- if(m_Selection.GetUpperLeft() == m_Selection.GetLowerRight() && !validChans.empty())
- {
- // No selection has been made: Interpolate between closest non-zero values in this column.
- PatternRect sweepSelection;
- switch(type)
- {
- case PatternCursor::noteColumn:
- // Allow note-to-note interpolation only.
- sweepSelection = SweepPattern(
- [](const ModCommand &start) { return start.note != NOTE_NONE; },
- [](const ModCommand &start, const ModCommand &end) { return start.IsNote() && end.IsNote(); });
- break;
- case PatternCursor::instrColumn:
- // Allow interpolation between same instrument, as long as it's not a PC note.
- sweepSelection = SweepPattern(
- [](const ModCommand &start) { return start.instr != 0 && !start.IsPcNote(); },
- [](const ModCommand &start, const ModCommand &end) { return end.instr == start.instr; });
- break;
- case PatternCursor::volumeColumn:
- // Allow interpolation between same volume effect, as long as it's not a PC note.
- sweepSelection = SweepPattern(
- [](const ModCommand &start) { return start.volcmd != VOLCMD_NONE && !start.IsPcNote(); },
- [](const ModCommand &start, const ModCommand &end) { return end.volcmd == start.volcmd && !end.IsPcNote(); });
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- // Allow interpolation between same effect, or anything if it's a PC note.
- sweepSelection = SweepPattern(
- [](const ModCommand &start) { return start.command != CMD_NONE || start.IsPcNote(); },
- [](const ModCommand &start, const ModCommand &end) { return (end.command == start.command || start.IsPcNote()) && (!start.IsPcNote() || end.IsPcNote()); });
- break;
- }
- if(sweepSelection.GetNumRows() > 1)
- {
- // Found usable end and start commands: Extend selection.
- SetCurSel(sweepSelection);
- }
- }
- const ROWINDEX row0 = m_Selection.GetStartRow(), row1 = m_Selection.GetEndRow();
- //for all channels where type is selected
- for(auto nchn : validChans)
- {
- if(!IsInterpolationPossible(row0, row1, nchn, type))
- continue; //skip chans where interpolation isn't possible
- if(!changed) //ensure we save undo buffer only before any channels are interpolated
- {
- const char *description = "";
- switch(type)
- {
- case PatternCursor::noteColumn:
- description = "Interpolate Note Column";
- break;
- case PatternCursor::instrColumn:
- description = "Interpolate Instrument Column";
- break;
- case PatternCursor::volumeColumn:
- description = "Interpolate Volume Column";
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- description = "Interpolate Effect Column";
- break;
- }
- PrepareUndo(m_Selection, description);
- }
- bool doPCinterpolation = false;
- int vsrc, vdest, vcmd = 0, verr = 0, distance = row1 - row0;
- const ModCommand srcCmd = *sndFile->Patterns[m_nPattern].GetpModCommand(row0, nchn);
- const ModCommand destCmd = *sndFile->Patterns[m_nPattern].GetpModCommand(row1, nchn);
- ModCommand::NOTE PCnote = NOTE_NONE;
- uint16 PCinst = 0, PCparam = 0;
- switch(type)
- {
- case PatternCursor::noteColumn:
- vsrc = srcCmd.note;
- vdest = destCmd.note;
- vcmd = srcCmd.instr;
- verr = (distance * (NOTE_MAX - 1)) / NOTE_MAX;
- if(srcCmd.note == NOTE_NONE)
- {
- vsrc = vdest;
- vcmd = destCmd.note;
- } else if(destCmd.note == NOTE_NONE)
- {
- vdest = vsrc;
- }
- break;
- case PatternCursor::instrColumn:
- vsrc = srcCmd.instr;
- vdest = destCmd.instr;
- verr = (distance * 63) / 128;
- if(srcCmd.instr == 0)
- {
- vsrc = vdest;
- vcmd = destCmd.instr;
- } else if(destCmd.instr == 0)
- {
- vdest = vsrc;
- }
- break;
- case PatternCursor::volumeColumn:
- vsrc = srcCmd.vol;
- vdest = destCmd.vol;
- vcmd = srcCmd.volcmd;
- verr = (distance * 63) / 128;
- if(srcCmd.volcmd == VOLCMD_NONE)
- {
- vcmd = destCmd.volcmd;
- if(vcmd == VOLCMD_VOLUME && srcCmd.IsNote() && srcCmd.instr)
- vsrc = GetDefaultVolume(srcCmd);
- else
- vsrc = vdest;
- } else if(destCmd.volcmd == VOLCMD_NONE)
- {
- if(vcmd == VOLCMD_VOLUME && destCmd.IsNote() && destCmd.instr)
- vdest = GetDefaultVolume(srcCmd);
- else
- vdest = vsrc;
- }
- break;
- case PatternCursor::paramColumn:
- case PatternCursor::effectColumn:
- if(srcCmd.IsPcNote() || destCmd.IsPcNote())
- {
- doPCinterpolation = true;
- PCnote = (srcCmd.IsPcNote()) ? srcCmd.note : destCmd.note;
- vsrc = srcCmd.GetValueEffectCol();
- vdest = destCmd.GetValueEffectCol();
- PCparam = srcCmd.GetValueVolCol();
- if((PCparam == 0 && destCmd.IsPcNote()) || !srcCmd.IsPcNote())
- PCparam = destCmd.GetValueVolCol();
- PCinst = srcCmd.instr;
- if(PCinst == 0)
- PCinst = destCmd.instr;
- } else
- {
- vsrc = srcCmd.param;
- vdest = destCmd.param;
- vcmd = srcCmd.command;
- if(srcCmd.command == CMD_NONE)
- {
- vsrc = vdest;
- vcmd = destCmd.command;
- } else if(destCmd.command == CMD_NONE)
- {
- vdest = vsrc;
- }
- }
- verr = (distance * 63) / 128;
- break;
- default:
- MPT_ASSERT(false);
- return;
- }
- if(vdest < vsrc)
- verr = -verr;
- ModCommand *pcmd = sndFile->Patterns[m_nPattern].GetpModCommand(row0, nchn);
- for(int i = 0; i <= distance; i++, pcmd += sndFile->GetNumChannels())
- {
- switch(type)
- {
- case PatternCursor::noteColumn:
- if((pcmd->note == NOTE_NONE || pcmd->instr == vcmd) && !pcmd->IsPcNote())
- {
- int note = vsrc + ((vdest - vsrc) * i + verr) / distance;
- pcmd->note = static_cast<ModCommand::NOTE>(note);
- if(pcmd->instr == 0)
- pcmd->instr = static_cast<ModCommand::VOLCMD>(vcmd);
- }
- break;
- case PatternCursor::instrColumn:
- if(pcmd->instr == 0)
- {
- int instr = vsrc + ((vdest - vsrc) * i + verr) / distance;
- pcmd->instr = static_cast<ModCommand::INSTR>(instr);
- }
- break;
- case PatternCursor::volumeColumn:
- if((pcmd->volcmd == VOLCMD_NONE || pcmd->volcmd == vcmd) && !pcmd->IsPcNote())
- {
- int vol = vsrc + ((vdest - vsrc) * i + verr) / distance;
- pcmd->vol = static_cast<ModCommand::VOL>(vol);
- pcmd->volcmd = static_cast<ModCommand::VOLCMD>(vcmd);
- }
- break;
- case PatternCursor::effectColumn:
- if(doPCinterpolation)
- { // With PC/PCs notes, copy PCs note and plug index to all rows where
- // effect interpolation is done if no PC note with non-zero instrument is there.
- const uint16 val = static_cast<uint16>(vsrc + ((vdest - vsrc) * i + verr) / distance);
- if(!pcmd->IsPcNote() || pcmd->instr == 0)
- {
- pcmd->note = PCnote;
- pcmd->instr = static_cast<ModCommand::INSTR>(PCinst);
- }
- pcmd->SetValueVolCol(PCparam);
- pcmd->SetValueEffectCol(val);
- } else if(!pcmd->IsPcNote())
- {
- if((pcmd->command == CMD_NONE) || (pcmd->command == vcmd))
- {
- int val = vsrc + ((vdest - vsrc) * i + verr) / distance;
- pcmd->param = static_cast<ModCommand::PARAM>(val);
- pcmd->command = static_cast<ModCommand::COMMAND>(vcmd);
- }
- }
- break;
- default:
- MPT_ASSERT(false);
- }
- }
- changed = true;
- } //end for all channels where type is selected
- if(changed)
- {
- SetModified(false);
- InvalidatePattern(false);
- }
- }
- void CViewPattern::OnResetChannelColors()
- {
- CModDoc &modDoc = *GetDocument();
- const CSoundFile &sndFile = *GetSoundFile();
- modDoc.GetPatternUndo().PrepareChannelUndo(0, sndFile.GetNumChannels(), "Reset Channel Colours");
- if(modDoc.SetDefaultChannelColors())
- {
- if(modDoc.SupportsChannelColors())
- modDoc.SetModified();
- modDoc.UpdateAllViews(nullptr, GeneralHint().Channels(), nullptr);
- } else
- {
- modDoc.GetPatternUndo().RemoveLastUndoStep();
- }
- }
- void CViewPattern::OnTransposeChannel()
- {
- CInputDlg dlg(this, _T("Enter transpose amount (affects all patterns):"), -(NOTE_MAX - NOTE_MIN), (NOTE_MAX - NOTE_MIN), m_nTransposeAmount);
- if(dlg.DoModal() == IDOK)
- {
- m_nTransposeAmount = dlg.resultAsInt;
- CSoundFile &sndFile = *GetSoundFile();
- bool changed = false;
- // Don't allow notes outside our supported note range.
- const ModCommand::NOTE noteMin = sndFile.GetModSpecifications().noteMin;
- const ModCommand::NOTE noteMax = sndFile.GetModSpecifications().noteMax;
- for(PATTERNINDEX pat = 0; pat < sndFile.Patterns.Size(); pat++)
- {
- bool changedThisPat = false;
- if(sndFile.Patterns.IsValidPat(pat))
- {
- ModCommand *m = sndFile.Patterns[pat].GetpModCommand(0, m_MenuCursor.GetChannel());
- const ROWINDEX numRows = sndFile.Patterns[pat].GetNumRows();
- for(ROWINDEX row = 0; row < numRows; row++)
- {
- if(m->IsNote())
- {
- if(!changedThisPat)
- {
- GetDocument()->GetPatternUndo().PrepareUndo(pat, m_MenuCursor.GetChannel(), 0, 1, numRows, "Transpose Channel", changed);
- changed = changedThisPat = true;
- }
- int note = m->note + m_nTransposeAmount;
- Limit(note, noteMin, noteMax);
- m->note = static_cast<ModCommand::NOTE>(note);
- }
- m += sndFile.Patterns[pat].GetNumChannels();
- }
- }
- }
- if(changed)
- {
- SetModified(true);
- InvalidatePattern(false);
- }
- }
- }
- void CViewPattern::OnTransposeCustom()
- {
- CInputDlg dlg(this, _T("Enter transpose amount:"), -(NOTE_MAX - NOTE_MIN), (NOTE_MAX - NOTE_MIN), m_nTransposeAmount);
- if(dlg.DoModal() == IDOK)
- {
- m_nTransposeAmount = dlg.resultAsInt;
- TransposeSelection(dlg.resultAsInt);
- }
- }
- void CViewPattern::OnTransposeCustomQuick()
- {
- if(m_nTransposeAmount != 0)
- TransposeSelection(m_nTransposeAmount);
- else
- OnTransposeCustom();
- }
- bool CViewPattern::TransposeSelection(int transp)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return false;
- }
- m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
- // Don't allow notes outside our supported note range.
- const ModCommand::NOTE noteMin = pSndFile->GetModSpecifications().noteMin;
- const ModCommand::NOTE noteMax = pSndFile->GetModSpecifications().noteMax;
- PrepareUndo(m_Selection, "Transpose");
- std::vector<int> lastGroupSize(pSndFile->GetNumChannels(), 12);
- ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
- {
- if(chn == m_Selection.GetStartChannel() && m_Selection.GetStartColumn() > PatternCursor::noteColumn)
- return;
- if(m.IsNote())
- {
- if(m.instr > 0)
- {
- lastGroupSize[chn] = GetDocument()->GetInstrumentGroupSize(m.instr);
- }
- int transpose = transp;
- if(transpose == 12000 || transpose == -12000)
- {
- // Transpose one octave
- transpose = lastGroupSize[chn] * mpt::signum(transpose);
- }
- int note = m.note + transpose;
- Limit(note, noteMin, noteMax);
- m.note = static_cast<ModCommand::NOTE>(note);
- }
- });
- SetModified(false);
- InvalidateSelection();
- if(m_Selection.GetNumChannels() == 1 && m_Selection.GetNumRows() == 1 && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYTRANSPOSE))
- {
- // Preview a single transposed note
- PreviewNote(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
- }
- return true;
- }
- bool CViewPattern::DataEntry(bool up, bool coarse)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return false;
- }
- m_Selection.Sanitize(pSndFile->Patterns[m_nPattern].GetNumRows(), pSndFile->GetNumChannels());
- const PatternCursor::Columns column = m_Selection.GetStartColumn();
- // Don't allow notes outside our supported note range.
- const ModCommand::NOTE noteMin = pSndFile->GetModSpecifications().noteMin;
- const ModCommand::NOTE noteMax = pSndFile->GetModSpecifications().noteMax;
- const int instrMax = std::min(static_cast<int>(Util::MaxValueOfType(ModCommand::INSTR())), static_cast<int>(pSndFile->GetNumInstruments() ? pSndFile->GetNumInstruments() : pSndFile->GetNumSamples()));
- const EffectInfo effectInfo(*pSndFile);
- const int offset = up ? 1 : -1;
- PrepareUndo(m_Selection, "Data Entry");
- // Notes per octave for non-TET12 tunings and coarse note steps
- std::vector<int> lastGroupSize(pSndFile->GetNumChannels(), 12);
- bool applyToSpecialNotes = true;
- if(column == PatternCursor::noteColumn)
- {
- const CPattern &pattern = pSndFile->Patterns[m_nPattern];
- const CHANNELINDEX startChn = m_Selection.GetStartChannel(), endChn = m_Selection.GetEndChannel();
- const ROWINDEX endRow = m_Selection.GetEndRow();
- for(ROWINDEX row = m_Selection.GetStartRow(); row <= endRow && applyToSpecialNotes; row++)
- {
- const ModCommand *m = pattern.GetpModCommand(row, startChn);
- for(CHANNELINDEX chn = startChn; chn <= endChn; chn++, m++)
- {
- if(!m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::noteColumn)))
- continue;
- if(m->IsNote())
- {
- applyToSpecialNotes = false;
- break;
- }
- }
- }
- }
- ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
- {
- if(column == PatternCursor::noteColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::noteColumn)))
- {
- // Increase / decrease note
- if(m.IsNote() && !applyToSpecialNotes)
- {
- if(m.instr > 0)
- {
- lastGroupSize[chn] = GetDocument()->GetInstrumentGroupSize(m.instr);
- }
- int note = m.note + offset * (coarse ? lastGroupSize[chn] : 1);
- Limit(note, noteMin, noteMax);
- m.note = (ModCommand::NOTE)note;
- } else if(m.IsSpecialNote() && applyToSpecialNotes)
- {
- ModCommand::NOTE note = m.note;
- do
- {
- note = static_cast<ModCommand::NOTE>(note + offset);
- if(!ModCommand::IsSpecialNote(note))
- {
- break;
- }
- } while(!pSndFile->GetModSpecifications().HasNote(note));
- if(ModCommand::IsSpecialNote(note))
- {
- if(m.IsPcNote() != ModCommand::IsPcNote(note))
- {
- m.Clear();
- }
- m.note = (ModCommand::NOTE)note;
- }
- }
- }
- if(column == PatternCursor::instrColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::instrColumn)) && m.instr != 0)
- {
- // Increase / decrease instrument
- int instr = m.instr + offset * (coarse ? 10 : 1);
- Limit(instr, 1, m.IsInstrPlug() ? MAX_MIXPLUGINS : instrMax);
- m.instr = (ModCommand::INSTR)instr;
- }
- if(column == PatternCursor::volumeColumn && m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::volumeColumn)))
- {
- // Increase / decrease volume parameter
- if(m.IsPcNote())
- {
- int val = m.GetValueVolCol() + offset * (coarse ? 10 : 1);
- Limit(val, 0, ModCommand::maxColumnValue);
- m.SetValueVolCol(static_cast<uint16>(val));
- } else
- {
- int vol = m.vol + offset * (coarse ? 10 : 1);
- if(m.volcmd == VOLCMD_NONE && m.IsNote() && m.instr)
- {
- m.volcmd = VOLCMD_VOLUME;
- vol = GetDefaultVolume(m);
- }
- ModCommand::VOL minValue = 0, maxValue = 64;
- effectInfo.GetVolCmdInfo(effectInfo.GetIndexFromVolCmd(m.volcmd), nullptr, &minValue, &maxValue);
- Limit(vol, (int)minValue, (int)maxValue);
- m.vol = (ModCommand::VOL)vol;
- }
- }
- if((column == PatternCursor::effectColumn || column == PatternCursor::paramColumn) && (m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::effectColumn)) || m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::paramColumn))))
- {
- // Increase / decrease effect parameter
- if(m.IsPcNote())
- {
- int val = m.GetValueEffectCol() + offset * (coarse ? 10 : 1);
- Limit(val, 0, ModCommand::maxColumnValue);
- m.SetValueEffectCol(static_cast<uint16>(val));
- } else
- {
- int param = m.param + offset * (coarse ? 16 : 1);
- ModCommand::PARAM minValue = 0x00, maxValue = 0xFF;
- if(!m.IsSlideUpDownCommand())
- {
- const auto effectIndex = effectInfo.GetIndexFromEffect(m.command, m.param);
- effectInfo.GetEffectInfo(effectIndex, nullptr, false, &minValue, &maxValue);
- minValue = static_cast<ModCommand::PARAM>(effectInfo.MapPosToValue(effectIndex, minValue));
- maxValue = static_cast<ModCommand::PARAM>(effectInfo.MapPosToValue(effectIndex, maxValue));
- }
- m.param = static_cast<ModCommand::PARAM>(Clamp(param, minValue, maxValue));
- }
- }
- });
- SetModified(false);
- InvalidatePattern();
- if(column == PatternCursor::noteColumn && m_Selection.GetNumChannels() == 1 && m_Selection.GetNumRows() == 1 && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYTRANSPOSE))
- {
- // Preview a single transposed note
- PreviewNote(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
- }
- return true;
- }
- // Get the velocity at which a given note would be played
- int CViewPattern::GetDefaultVolume(const ModCommand &m, ModCommand::INSTR lastInstr) const
- {
- const CSoundFile &sndFile = *GetSoundFile();
- SAMPLEINDEX sample = GetDocument()->GetSampleIndex(m, lastInstr);
- if(sample)
- return std::min(sndFile.GetSample(sample).nVolume, uint16(256)) / 4u;
- else if(m.instr > 0 && m.instr <= sndFile.GetNumInstruments() && sndFile.Instruments[m.instr] != nullptr && sndFile.Instruments[m.instr]->HasValidMIDIChannel())
- return std::min(sndFile.Instruments[m.instr]->nGlobalVol, uint32(64)); // For instrument plugins
- else
- return 64;
- }
- int CViewPattern::GetBaseNote() const
- {
- const CModDoc *modDoc = GetDocument();
- INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
- if(!instr && !IsLiveRecord())
- instr = GetCursorCommand().instr;
- return modDoc->GetBaseNote(instr);
- }
- ModCommand::NOTE CViewPattern::GetNoteWithBaseOctave(int note) const
- {
- const CModDoc *modDoc = GetDocument();
- INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
- if(!instr && !IsLiveRecord())
- instr = GetCursorCommand().instr;
- return modDoc->GetNoteWithBaseOctave(note, instr);
- }
- void CViewPattern::OnDropSelection()
- {
- CModDoc *pModDoc;
- if((pModDoc = GetDocument()) == nullptr || !IsEditingEnabled_bmsg())
- {
- return;
- }
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- // Compute relative movement
- int dx = (int)m_DragPos.GetChannel() - (int)m_StartSel.GetChannel();
- int dy = (int)m_DragPos.GetRow() - (int)m_StartSel.GetRow();
- if((!dx) && (!dy))
- {
- return;
- }
- // Allocate replacement pattern
- CPattern &pattern = sndFile.Patterns[m_nPattern];
- auto origPattern = pattern.GetData();
- // Compute destination rect
- PatternCursor begin(m_Selection.GetUpperLeft()), end(m_Selection.GetLowerRight());
- begin.Move(dy, dx, 0);
- if(begin.GetChannel() >= sndFile.GetNumChannels())
- {
- // Moved outside pattern range.
- return;
- }
- end.Move(dy, dx, 0);
- if(end.GetColumnType() == PatternCursor::effectColumn)
- {
- // Extend to parameter column
- end.Move(0, 0, 1);
- }
- begin.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
- end.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
- PatternRect destination(begin, end);
- const bool moveSelection = !m_Status[psKeyboardDragSelect | psCtrlDragSelect];
- BeginWaitCursor();
- pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, 0, 0, sndFile.GetNumChannels(), pattern.GetNumRows(), moveSelection ? "Move Selection" : "Copy Selection");
- const ModCommand empty = ModCommand::Empty();
- auto p = pattern.begin();
- for(ROWINDEX row = 0; row < pattern.GetNumRows(); row++)
- {
- for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++, p++)
- {
- for(int c = PatternCursor::firstColumn; c <= PatternCursor::lastColumn; c++)
- {
- PatternCursor cell(row, chn, static_cast<PatternCursor::Columns>(c));
- int xsrc = chn, ysrc = row;
- if(destination.Contains(cell))
- {
- // Current cell is from destination selection
- xsrc -= dx;
- ysrc -= dy;
- } else if(m_Selection.Contains(cell))
- {
- // Current cell is from source rectangle (clear)
- if(moveSelection)
- {
- xsrc = -1;
- }
- } else
- {
- continue;
- }
- // Copy the data
- const ModCommand &src = (xsrc >= 0 && xsrc < (int)sndFile.GetNumChannels() && ysrc >= 0 && ysrc < (int)sndFile.Patterns[m_nPattern].GetNumRows()) ? origPattern[ysrc * sndFile.GetNumChannels() + xsrc] : empty;
- switch(c)
- {
- case PatternCursor::noteColumn:
- p->note = src.note;
- break;
- case PatternCursor::instrColumn:
- p->instr = src.instr;
- break;
- case PatternCursor::volumeColumn:
- p->vol = src.vol;
- p->volcmd = src.volcmd;
- break;
- case PatternCursor::effectColumn:
- p->command = src.command;
- p->param = src.param;
- break;
- }
- }
- }
- }
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(begin);
- SetCurSel(destination);
- InvalidatePattern();
- SetModified(false);
- EndWaitCursor();
- }
- void CViewPattern::OnSetSelInstrument()
- {
- SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(GetCurrentInstrument()), false);
- }
- void CViewPattern::OnRemoveChannelDialog()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- pModDoc->ChangeNumChannels(0);
- SetCurrentPattern(m_nPattern); //Updating the screen.
- }
- void CViewPattern::OnRemoveChannel()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- const CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(sndFile.GetNumChannels() <= sndFile.GetModSpecifications().channelsMin)
- {
- Reporting::Error("No channel removed - channel number already at minimum.", "Remove channel");
- return;
- }
- CHANNELINDEX nChn = m_MenuCursor.GetChannel();
- const bool isEmpty = pModDoc->IsChannelUnused(nChn);
- CString str;
- str.Format(_T("Remove channel %d? This channel still contains note data!"), nChn + 1);
- if(isEmpty || Reporting::Confirm(str, "Remove channel") == cnfYes)
- {
- std::vector<bool> keepMask(pModDoc->GetNumChannels(), true);
- keepMask[nChn] = false;
- pModDoc->RemoveChannels(keepMask, true);
- SetCurrentPattern(m_nPattern); //Updating the screen.
- pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this);
- }
- }
- void CViewPattern::AddChannel(CHANNELINDEX parent, bool afterCurrent)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- BeginWaitCursor();
- // Create new channel order, with channel nBefore being an invalid (and thus empty) channel.
- std::vector<CHANNELINDEX> channels(pModDoc->GetNumChannels() + 1, CHANNELINDEX_INVALID);
- CHANNELINDEX i = 0;
- for(CHANNELINDEX nChn = 0; nChn < pModDoc->GetNumChannels() + 1; nChn++)
- {
- if(nChn != (parent + (afterCurrent ? 1 : 0)))
- {
- channels[nChn] = i++;
- }
- }
- if(pModDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
- {
- auto &chnSettings = pModDoc->GetSoundFile().ChnSettings;
- chnSettings[parent + (afterCurrent ? 1 : 0)].color = chnSettings[parent + (afterCurrent ? 0 : 1)].color;
- pModDoc->SetModified();
- pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this); //refresh channel headers
- SetCurrentPattern(m_nPattern);
- }
- EndWaitCursor();
- }
- void CViewPattern::OnDuplicateChannel()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- const CHANNELINDEX dupChn = m_MenuCursor.GetChannel();
- if(dupChn >= pModDoc->GetNumChannels())
- return;
- if(!pModDoc->IsChannelUnused(dupChn) && Reporting::Confirm(_T("This affects all patterns, proceed?"), _T("Duplicate Channel")) != cnfYes)
- return;
- BeginWaitCursor();
- // Create new channel order, with channel nDupChn duplicated.
- std::vector<CHANNELINDEX> channels(pModDoc->GetNumChannels() + 1, 0);
- CHANNELINDEX i = 0;
- for(CHANNELINDEX nChn = 0; nChn < pModDoc->GetNumChannels() + 1; nChn++)
- {
- channels[nChn] = i;
- if(nChn != dupChn)
- i++;
- }
- // Check that duplication happened and in that case update.
- if(pModDoc->ReArrangeChannels(channels) != CHANNELINDEX_INVALID)
- {
- pModDoc->SetModified();
- pModDoc->UpdateAllViews(nullptr, GeneralHint().General().Channels(), this); //refresh channel headers
- SetCurrentPattern(m_nPattern);
- }
- EndWaitCursor();
- }
- void CViewPattern::OnRunScript()
- {
- ;
- }
- void CViewPattern::OnSwitchToOrderList()
- {
- PostCtrlMessage(CTRLMSG_SETFOCUS);
- }
- void CViewPattern::OnPrevOrder()
- {
- PostCtrlMessage(CTRLMSG_PREVORDER);
- }
- void CViewPattern::OnNextOrder()
- {
- PostCtrlMessage(CTRLMSG_NEXTORDER);
- }
- void CViewPattern::OnUpdateUndo(CCmdUI *pCmdUI)
- {
- CModDoc *pModDoc = GetDocument();
- if((pCmdUI) && (pModDoc))
- {
- pCmdUI->Enable(pModDoc->GetPatternUndo().CanUndo());
- pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditUndo, _T("Undo ") + pModDoc->GetPatternUndo().GetUndoName()));
- }
- }
- void CViewPattern::OnUpdateRedo(CCmdUI *pCmdUI)
- {
- CModDoc *pModDoc = GetDocument();
- if((pCmdUI) && (pModDoc))
- {
- pCmdUI->Enable(pModDoc->GetPatternUndo().CanRedo());
- pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditRedo, _T("Redo ") + pModDoc->GetPatternUndo().GetRedoName()));
- }
- }
- void CViewPattern::OnEditUndo()
- {
- UndoRedo(true);
- }
- void CViewPattern::OnEditRedo()
- {
- UndoRedo(false);
- }
- void CViewPattern::UndoRedo(bool undo)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc && IsEditingEnabled_bmsg())
- {
- CHANNELINDEX oldNumChannels = pModDoc->GetNumChannels();
- PATTERNINDEX pat = undo ? pModDoc->GetPatternUndo().Undo() : pModDoc->GetPatternUndo().Redo();
- const CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(pat < sndFile.Patterns.Size())
- {
- if(pat != m_nPattern)
- {
- // Find pattern in sequence.
- ORDERINDEX matchingOrder = Order().FindOrder(pat, GetCurrentOrder());
- if(matchingOrder != ORDERINDEX_INVALID)
- {
- SetCurrentOrder(matchingOrder);
- }
- SetCurrentPattern(pat);
- } else
- {
- InvalidatePattern(true, true);
- }
- SetModified(false);
- SanitizeCursor();
- UpdateScrollSize();
- }
- if(oldNumChannels != pModDoc->GetNumChannels())
- {
- pModDoc->UpdateAllViews(this, GeneralHint().Channels().ModType(), this);
- }
- }
- }
- // Apply amplification and fade function to volume
- static void AmplifyFade(int &vol, int amp, ROWINDEX row, ROWINDEX numRows, int fadeIn, int fadeOut, Fade::Func &fadeFunc)
- {
- const bool doFadeIn = fadeIn != amp, doFadeOut = fadeOut != amp;
- const double fadeStart = fadeIn / 100.0, fadeStartDiff = (amp - fadeIn) / 100.0;
- const double fadeEnd = fadeOut / 100.0, fadeEndDiff = (amp - fadeOut) / 100.0;
- double l;
- if(doFadeIn && doFadeOut)
- {
- ROWINDEX numRows2 = numRows / 2;
- if(row < numRows2)
- l = fadeStart + fadeFunc(static_cast<double>(row) / numRows2) * fadeStartDiff;
- else
- l = fadeEnd + fadeFunc(static_cast<double>(numRows - row) / (numRows - numRows2)) * fadeEndDiff;
- } else if(doFadeIn)
- {
- l = fadeStart + fadeFunc(static_cast<double>(row + 1) / numRows) * fadeStartDiff;
- } else if(doFadeOut)
- {
- l = fadeEnd + fadeFunc(static_cast<double>(numRows - row) / numRows) * fadeEndDiff;
- } else
- {
- l = amp / 100.0;
- }
- vol = mpt::saturate_round<int>(vol * l);
- Limit(vol, 0, 64);
- }
- void CViewPattern::OnPatternAmplify()
- {
- static CAmpDlg::AmpSettings settings{Fade::kLinear, 0, 0, 100, false, false};
- CAmpDlg dlg(this, settings, 0);
- if(dlg.DoModal() != IDOK)
- {
- return;
- }
- CSoundFile &sndFile = *GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- return;
- const bool useVolCol = sndFile.GetModSpecifications().HasVolCommand(VOLCMD_VOLUME);
- BeginWaitCursor();
- PrepareUndo(m_Selection, "Amplify");
- m_Selection.Sanitize(sndFile.Patterns[m_nPattern].GetNumRows(), sndFile.GetNumChannels());
- const CHANNELINDEX firstChannel = m_Selection.GetStartChannel(), lastChannel = m_Selection.GetEndChannel();
- const ROWINDEX firstRow = m_Selection.GetStartRow(), lastRow = m_Selection.GetEndRow();
- // For partically selected start and end channels, we check if the start and end columns contain the relevant columns.
- bool firstChannelValid, lastChannelValid;
- if(useVolCol)
- {
- // Volume column
- firstChannelValid = m_Selection.ContainsHorizontal(PatternCursor(0, firstChannel, PatternCursor::volumeColumn));
- lastChannelValid = m_Selection.ContainsHorizontal(PatternCursor(0, lastChannel, PatternCursor::volumeColumn));
- } else
- {
- // Effect column
- firstChannelValid = true; // We cannot start "too far right" in the channel, since this is the last column.
- lastChannelValid = m_Selection.GetLowerRight().CompareColumn(PatternCursor(0, lastChannel, PatternCursor::effectColumn)) >= 0;
- }
- // Adjust min/max channel if they're only partly selected (i.e. volume column or effect column (when using .MOD) is not covered)
- // XXX if only the effect column is marked in the XM format, we cannot amplify volume commands there. Does anyone use that?
- if((!firstChannelValid && firstChannel >= lastChannel) || (!lastChannelValid && lastChannel <= firstChannel))
- {
- EndWaitCursor();
- return;
- }
- // Volume memory for each channel.
- std::vector<ModCommand::VOL> chvol(lastChannel + 1, 64);
- // First, fill the volume memory in case we start the selection before some note
- ApplyToSelection([&] (ModCommand &m, ROWINDEX, CHANNELINDEX chn)
- {
- if((chn == firstChannel && !firstChannelValid) || (chn == lastChannel && !lastChannelValid))
- return;
- if(m.command == CMD_VOLUME)
- chvol[chn] = std::min(m.param, ModCommand::PARAM(64));
- else if(m.volcmd == VOLCMD_VOLUME)
- chvol[chn] = m.vol;
- else if(m.instr != 0)
- chvol[chn] = static_cast<ModCommand::VOL>(GetDefaultVolume(m));
- });
- Fade::Func fadeFunc = GetFadeFunc(settings.fadeLaw);
- // Now do the actual amplification
- const int cy = lastRow - firstRow + 1; // total rows (for fading)
- ApplyToSelection([&] (ModCommand &m, ROWINDEX nRow, CHANNELINDEX chn)
- {
- if((chn == firstChannel && !firstChannelValid) || (chn == lastChannel && !lastChannelValid))
- return;
- if(m.command == CMD_VOLUME)
- chvol[chn] = std::min(m.param, ModCommand::PARAM(64));
- else if(m.volcmd == VOLCMD_VOLUME)
- chvol[chn] = m.vol;
- else if(m.instr != 0)
- chvol[chn] = static_cast<ModCommand::VOL>(GetDefaultVolume(m));
- if(settings.fadeIn || settings.fadeOut || (m.IsNote() && m.instr != 0))
- {
- // Insert new volume commands where necessary
- if(useVolCol && m.volcmd == VOLCMD_NONE)
- {
- m.volcmd = VOLCMD_VOLUME;
- m.vol = chvol[chn];
- } else if(!useVolCol && m.command == CMD_NONE)
- {
- m.command = CMD_VOLUME;
- m.param = chvol[chn];
- }
- }
- if(m.volcmd == VOLCMD_VOLUME)
- {
- int vol = m.vol;
- AmplifyFade(vol, settings.factor, nRow - firstRow, cy, settings.fadeIn ? settings.fadeInStart : settings.factor, settings.fadeOut ? settings.fadeOutEnd : settings.factor, fadeFunc);
- m.vol = static_cast<ModCommand::VOL>(vol);
- }
- if(m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::effectColumn)) || m_Selection.ContainsHorizontal(PatternCursor(0, chn, PatternCursor::paramColumn)))
- {
- if(m.command == CMD_VOLUME && m.param <= 64)
- {
- int vol = m.param;
- AmplifyFade(vol, settings.factor, nRow - firstRow, cy, settings.fadeIn ? settings.fadeInStart : settings.factor, settings.fadeOut ? settings.fadeOutEnd : settings.factor, fadeFunc);
- m.param = static_cast<ModCommand::PARAM>(vol);
- }
- }
- });
- SetModified(false);
- InvalidateSelection();
- EndWaitCursor();
- }
- LRESULT CViewPattern::OnPlayerNotify(Notification *pnotify)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || pnotify == nullptr)
- {
- return 0;
- }
- if(pnotify->type[Notification::Position])
- {
- ORDERINDEX ord = pnotify->order;
- ROWINDEX row = pnotify->row;
- PATTERNINDEX pat = pnotify->pattern;
- bool updateOrderList = false;
- if(m_nLastPlayedOrder != ord)
- {
- updateOrderList = true;
- m_nLastPlayedOrder = ord;
- }
- if(row < m_nLastPlayedRow)
- {
- InvalidateChannelsHeaders();
- }
- m_nLastPlayedRow = row;
- if(!pSndFile->m_SongFlags[SONG_PAUSED | SONG_STEP])
- {
- const auto &order = Order();
- if(ord >= order.GetLength() || order[ord] != pat)
- {
- //order doesn't correlate with pattern, so mark it as invalid
- ord = ORDERINDEX_INVALID;
- }
- if(m_pEffectVis && m_pEffectVis->m_hWnd)
- {
- m_pEffectVis->SetPlayCursor(pat, row);
- }
- // Simple detection of backwards-going patterns to avoid jerky animation
- m_nNextPlayRow = ROWINDEX_INVALID;
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SMOOTHSCROLL) && pSndFile->Patterns.IsValidPat(pat) && pSndFile->Patterns[pat].IsValidRow(row))
- {
- for(const ModCommand *m = pSndFile->Patterns[pat].GetRow(row), *mEnd = m + pSndFile->GetNumChannels(); m != mEnd; m++)
- {
- if(m->command == CMD_PATTERNBREAK)
- m_nNextPlayRow = m->param;
- else if(m->command == CMD_POSITIONJUMP && (m_nNextPlayRow == ROWINDEX_INVALID || pSndFile->GetType() == MOD_TYPE_XM))
- m_nNextPlayRow = 0;
- }
- }
- if(m_nNextPlayRow == ROWINDEX_INVALID)
- m_nNextPlayRow = row + 1;
- m_nTicksOnRow = pnotify->ticksOnRow;
- SetPlayCursor(pat, row, pnotify->tick);
- // Don't follow song if user drags selections or scrollbars.
- if((m_Status & (psFollowSong | psDragActive)) == psFollowSong)
- {
- if(pat < pSndFile->Patterns.Size())
- {
- if(pat != m_nPattern || ord != m_nOrder || updateOrderList)
- {
- if(pat != m_nPattern)
- SetCurrentPattern(pat, row);
- else if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SHOWPREVIOUS)
- InvalidatePattern(true, true); // Redraw previous / next pattern
- if(ord < order.GetLength())
- {
- m_nOrder = ord;
- SendCtrlMessage(CTRLMSG_NOTIFYCURRENTORDER, ord);
- }
- updateOrderList = false;
- }
- if(row != GetCurrentRow())
- {
- SetCurrentRow((row < pSndFile->Patterns[pat].GetNumRows()) ? row : 0, false, false);
- }
- }
- } else
- {
- if(updateOrderList)
- {
- SendCtrlMessage(CTRLMSG_FORCEREFRESH); //force orderlist refresh
- updateOrderList = false;
- }
- }
- }
- }
- if(pnotify->type[Notification::VUMeters | Notification::Stop] && m_Status[psShowVUMeters])
- {
- UpdateAllVUMeters(pnotify);
- }
- if(pnotify->type[Notification::Stop])
- {
- m_baPlayingNote.reset();
- ChnVUMeters.fill(0); // Also zero all non-visible VU meters
- SetPlayCursor(PATTERNINDEX_INVALID, ROWINDEX_INVALID, 0);
- }
- UpdateIndicator(false);
- return 0;
- }
- // record plugin parameter changes into current pattern
- LRESULT CViewPattern::OnRecordPlugParamChange(WPARAM plugSlot, LPARAM paramIndex)
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr || !IsEditingEnabled())
- return 0;
-
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- //Work out where to put the new data
- const PatternEditPos editPos = GetEditPos(sndFile, IsLiveRecord());
- const CHANNELINDEX chn = editPos.channel;
- const ROWINDEX row = editPos.row;
- const PATTERNINDEX pattern = editPos.pattern;
- ModCommand &mSrc = *sndFile.Patterns[pattern].GetpModCommand(row, chn);
- ModCommand m = mSrc;
- // TODO: Is the right plugin active? Move to a chan with the right plug
- // Probably won't do this - finish fluctuator implementation instead.
- IMixPlugin *pPlug = sndFile.m_MixPlugins[plugSlot].pMixPlugin;
- if(pPlug == nullptr)
- return 0;
- if(sndFile.GetModSpecifications().HasNote(NOTE_PCS))
- {
- // MPTM: Use PC Notes
- // only overwrite existing PC Notes
- if(m.IsEmpty() || m.IsPcNote())
- {
- 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));
- }
- } else if(sndFile.GetModSpecifications().HasCommand(CMD_SMOOTHMIDI))
- {
- // Other formats: Use MIDI macros
- // Figure out which plug param (if any) is controllable using the active macro on this channel.
- int activePlugParam = -1;
- auto activeMacro = sndFile.m_PlayState.Chn[chn].nActiveMacro;
- if(sndFile.m_MidiCfg.GetParameteredMacroType(activeMacro) == kSFxPlugParam)
- activePlugParam = sndFile.m_MidiCfg.MacroToPlugParam(activeMacro);
- // If the wrong macro is active, see if we can find the right one.
- // If we can, activate it for this chan by writing appropriate SFx command it.
- if(activePlugParam != paramIndex)
- {
- int foundMacro = sndFile.m_MidiCfg.FindMacroForParam(static_cast<PlugParamIndex>(paramIndex));
- if(foundMacro >= 0)
- {
- sndFile.m_PlayState.Chn[chn].nActiveMacro = static_cast<uint8>(foundMacro);
- if(m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI) //we overwrite existing Zxx and \xx only.
- {
- m.command = CMD_S3MCMDEX;
- if(!sndFile.GetModSpecifications().HasCommand(CMD_S3MCMDEX))
- m.command = CMD_MODCMDEX;
- m.param = 0xF0 | (foundMacro & 0x0F);
- }
- }
- }
- // Write the data, but we only overwrite if the command is a macro anyway.
- if(m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI)
- {
- m.command = CMD_SMOOTHMIDI;
- PlugParamValue param = pPlug->GetParameter(static_cast<PlugParamIndex>(paramIndex));
- Limit(param, 0.0f, 1.0f);
- m.param = static_cast<ModCommand::PARAM>(param * 127.0f);
- }
- }
- if(m != mSrc)
- {
- pModDoc->GetPatternUndo().PrepareUndo(pattern, chn, row, 1, 1, "Automation Entry");
- mSrc = m;
- InvalidateCell(PatternCursor(row, chn));
- SetModified(false);
- }
- return 0;
- }
- PatternEditPos CViewPattern::GetEditPos(const CSoundFile &sndFile, const bool liveRecord) const
- {
- PatternEditPos editPos;
- if(liveRecord)
- {
- if(m_nPlayPat != PATTERNINDEX_INVALID)
- {
- editPos.row = m_nPlayRow;
- editPos.order = GetCurrentOrder();
- editPos.pattern = m_nPlayPat;
- } else
- {
- editPos.row = sndFile.m_PlayState.m_nRow;
- editPos.order = sndFile.m_PlayState.m_nCurrentOrder;
- editPos.pattern = sndFile.m_PlayState.m_nPattern;
- }
- if(!sndFile.Patterns.IsValidPat(editPos.pattern) || !sndFile.Patterns[editPos.pattern].IsValidRow(editPos.row))
- {
- editPos.row = GetCurrentRow();
- editPos.order = GetCurrentOrder();
- editPos.pattern = m_nPattern;
- }
- const auto &order = Order();
- if(!order.IsValidPat(editPos.order) || order[editPos.order] != editPos.pattern)
- {
- ORDERINDEX realOrder = order.FindOrder(editPos.pattern, editPos.order);
- if(realOrder != ORDERINDEX_INVALID)
- editPos.order = realOrder;
- }
- } else
- {
- editPos.row = GetCurrentRow();
- editPos.order = GetCurrentOrder();
- editPos.pattern = m_nPattern;
- }
- editPos.channel = GetCurrentChannel();
- return editPos;
- }
- // Return ModCommand at the given cursor position of the current pattern.
- // If the position is not valid, a pointer to a dummy command is returned.
- ModCommand &CViewPattern::GetModCommand(PatternCursor cursor)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(GetCurrentPattern()) && pSndFile->Patterns[GetCurrentPattern()].IsValidRow(cursor.GetRow()))
- {
- return *pSndFile->Patterns[GetCurrentPattern()].GetpModCommand(cursor.GetRow(), cursor.GetChannel());
- }
- // Failed.
- static ModCommand dummy;
- return dummy;
- }
- // Sanitize cursor so that it can't point to an invalid position in the current pattern.
- void CViewPattern::SanitizeCursor()
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(GetCurrentPattern()))
- {
- m_Cursor.Sanitize(GetSoundFile()->Patterns[m_nPattern].GetNumRows(), GetSoundFile()->Patterns[m_nPattern].GetNumChannels());
- }
- };
- // Returns pointer to modcommand at given position.
- // If the position is not valid, a pointer to a dummy command is returned.
- ModCommand &CViewPattern::GetModCommand(CSoundFile &sndFile, const PatternEditPos &pos)
- {
- static ModCommand dummy;
- if(sndFile.Patterns.IsValidPat(pos.pattern) && pos.row < sndFile.Patterns[pos.pattern].GetNumRows() && pos.channel < sndFile.GetNumChannels())
- return *sndFile.Patterns[pos.pattern].GetpModCommand(pos.row, pos.channel);
- else
- return dummy;
- }
- LRESULT CViewPattern::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM)
- {
- const uint32 midiData = static_cast<uint32>(dwMidiDataParam);
- static uint8 midiVolume = 127;
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr || pMainFrm == nullptr)
- return 0;
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- //Midi message from our perspective:
- // +---------------------------+---------------------------+-------------+-------------+
- //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 |
- // +---------------------------+---------------------------+-------------+-------------+
- // | Velocity (0-127) | Note (middle C is 60) | Event | Channel |
- // +---------------------------+---------------------------+-------------+-------------+
- //(http://home.roadrunner.com/~jgglatt/tech/midispec.htm)
- //Notes:
- //. Initial midi data handling is done in MidiInCallBack().
- //. If no event is received, previous event is assumed.
- //. A note-on (event=9) with velocity 0 is equivalent to a note off.
- //. Basing the event solely on the velocity as follows is incorrect,
- // since a note-off can have a velocity too:
- // BYTE event = (dwMidiData>>16) & 0x64;
- //. Sample- and instrumentview handle midi mesages in their own methods.
- const uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData);
- const uint8 midiByte2 = MIDIEvents::GetDataByte2FromEvent(midiData);
- const uint8 channel = MIDIEvents::GetChannelFromEvent(midiData);
- const uint8 nNote = midiByte1 + NOTE_MIN;
- int vol = midiByte2; // At this stage nVol is a non linear value in [0;127]
- // Need to convert to linear in [0;64] - see below
- MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData);
- if((event == MIDIEvents::evNoteOn) && !vol)
- event = MIDIEvents::evNoteOff; //Convert event to note-off if req'd
- // Handle MIDI mapping.
- PLUGINDEX mappedIndex = uint8_max;
- PlugParamIndex paramIndex = 0;
- uint16 paramValue = uint16_max;
- bool captured = sndFile.GetMIDIMapper().OnMIDImsg(midiData, mappedIndex, paramIndex, paramValue);
- // Handle MIDI messages assigned to shortcuts
- CInputHandler *ih = CMainFrame::GetInputHandler();
- if(ih->HandleMIDIMessage(static_cast<InputTargetContext>(kCtxViewPatterns + 1 + m_Cursor.GetColumnType()), midiData) != kcNull
- || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull)
- {
- // Mapped to a command, no need to pass message on.
- captured = true;
- }
- // Write parameter control commands if needed.
- if(paramValue != uint16_max && IsEditingEnabled() && sndFile.GetType() == MOD_TYPE_MPT)
- {
- const bool liveRecord = IsLiveRecord();
- PatternEditPos editPos = GetEditPos(sndFile, liveRecord);
- ModCommand &m = GetModCommand(sndFile, editPos);
- pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, editPos.channel, editPos.row, 1, 1, "MIDI Mapping Record");
- m.Set(NOTE_PCS, mappedIndex, static_cast<uint16>(paramIndex), static_cast<uint16>((paramValue * ModCommand::maxColumnValue) / 16383));
- if(!liveRecord)
- InvalidateRow(editPos.row);
- pModDoc->SetModified();
- pModDoc->UpdateAllViews(this, PatternHint(editPos.pattern).Data(), this);
- }
- if(captured)
- {
- // Event captured by MIDI mapping or shortcut, no need to pass message on.
- return 1;
- }
- const auto &modSpecs = sndFile.GetModSpecifications();
- bool recordParamAsZxx = false;
- switch(event)
- {
- case MIDIEvents::evNoteOff: // Note Off
- if(m_midiSustainActive[channel])
- {
- m_midiSustainBuffer[channel].push_back(midiData);
- return 1;
- }
- // The following method takes care of:
- // . Silencing specific active notes (just setting nNote to 255 as was done before is not acceptible)
- // . Entering a note off in pattern if required
- TempStopNote(nNote, ((TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_RECORDNOTEOFF) != 0));
- break;
- case MIDIEvents::evNoteOn: // Note On
- // Continue playing as soon as MIDI notes are being received
- if((pMainFrm->GetSoundFilePlaying() != &sndFile || sndFile.IsPaused()) && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))
- pModDoc->OnPatternPlayNoLoop();
- vol = CMainFrame::ApplyVolumeRelatedSettings(midiData, midiVolume);
- if(vol < 0)
- vol = -1;
- else
- vol = (vol + 3) / 4; //Value from [0,256] to [0,64]
- TempEnterNote(nNote, vol, true);
- break;
- case MIDIEvents::evPolyAftertouch: // Polyphonic aftertouch
- EnterAftertouch(nNote, vol);
- break;
- case MIDIEvents::evChannelAftertouch: // Channel aftertouch
- EnterAftertouch(NOTE_NONE, midiByte1);
- break;
- case MIDIEvents::evPitchBend: // Pitch wheel
- recordParamAsZxx = (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIMACROPITCHBEND) != 0 || modSpecs.HasCommand(CMD_FINETUNE);
- break;
- case MIDIEvents::evControllerChange: //Controller change
- // Checking whether to record MIDI controller change as MIDI macro change.
- // Don't write this if command was already written by MIDI mapping.
- if((paramValue == uint16_max || sndFile.GetType() != MOD_TYPE_MPT)
- && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDIMACROCONTROL)
- && !TrackerSettings::Instance().midiIgnoreCCs.Get()[midiByte1 & 0x7F])
- {
- recordParamAsZxx = true;
- }
- switch(midiByte1)
- {
- case MIDIEvents::MIDICC_Volume_Coarse:
- midiVolume = midiByte2;
- break;
- case MIDIEvents::MIDICC_HoldPedal_OnOff:
- m_midiSustainActive[channel] = (midiByte2 >= 0x40);
- if(!m_midiSustainActive[channel])
- {
- // Release all notes
- for(const auto offEvent : m_midiSustainBuffer[channel])
- {
- OnMidiMsg(offEvent, 0);
- }
- m_midiSustainBuffer[channel].clear();
- }
- recordParamAsZxx = false;
- break;
- }
- break;
- case MIDIEvents::evSystem:
- if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_RESPONDTOPLAYCONTROLMSGS)
- {
- // Respond to MIDI song messages
- switch(channel)
- {
- case MIDIEvents::sysStart: //Start song
- pModDoc->OnPlayerPlayFromStart();
- break;
- case MIDIEvents::sysContinue: //Continue song
- pModDoc->OnPlayerPlay();
- break;
- case MIDIEvents::sysStop: //Stop song
- pModDoc->OnPlayerStop();
- break;
- }
- }
- break;
- }
- // Write CC or pitch bend message as MIDI macro change.
- if(recordParamAsZxx && IsEditingEnabled())
- {
- const bool liveRecord = IsLiveRecord();
- const auto editpos = GetEditPos(sndFile, liveRecord);
- ModCommand &m = GetModCommand(sndFile, editpos);
- bool update = false;
- if(event == MIDIEvents::evPitchBend && (m.command == CMD_NONE || m.command == CMD_FINETUNE || m.command == CMD_FINETUNE_SMOOTH) && modSpecs.HasCommand(CMD_FINETUNE))
- {
- pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
- m.command = (m.command == CMD_NONE) ? CMD_FINETUNE : CMD_FINETUNE_SMOOTH;
- m.param = (midiByte2 << 1) | (midiByte1 >> 7);
- update = true;
- } else if(m.IsPcNote())
- {
- pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
- m.SetValueEffectCol(static_cast<decltype(m.GetValueEffectCol())>(Util::muldivr(midiByte2, ModCommand::maxColumnValue, 127)));
- update = true;
- } else if((m.command == CMD_NONE || m.command == CMD_SMOOTHMIDI || m.command == CMD_MIDI)
- && (modSpecs.HasCommand(CMD_SMOOTHMIDI) || modSpecs.HasCommand(CMD_MIDI)))
- {
- // Write command only if there's no existing command or already a midi macro command.
- pModDoc->GetPatternUndo().PrepareUndo(editpos.pattern, editpos.channel, editpos.row, 1, 1, "MIDI Record Entry");
- m.command = modSpecs.HasCommand(CMD_SMOOTHMIDI) ? CMD_SMOOTHMIDI : CMD_MIDI;
- m.param = midiByte2;
- update = true;
- }
- if(update)
- {
- pModDoc->SetModified();
- pModDoc->UpdateAllViews(this, PatternHint(editpos.pattern).Data(), this);
- // Update GUI only if not recording live.
- if(!liveRecord)
- InvalidateRow(editpos.row);
- }
- }
- // Pass MIDI to plugin
- if(TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_MIDITOPLUG
- && pMainFrm->GetModPlaying() == pModDoc
- && event != MIDIEvents::evNoteOn
- && event != MIDIEvents::evNoteOff)
- {
- const INSTRUMENTINDEX instr = static_cast<INSTRUMENTINDEX>(GetCurrentInstrument());
- IMixPlugin *plug = sndFile.GetInstrumentPlugin(instr);
- if(plug)
- {
- plug->MidiSend(midiData);
- // Sending MIDI may modify the plugin. For now, if MIDI data
- // is not active sensing, set modified.
- if(midiData != MIDIEvents::System(MIDIEvents::sysActiveSense))
- pModDoc->SetModified();
- }
- }
- return 1;
- }
- LRESULT CViewPattern::OnModViewMsg(WPARAM wParam, LPARAM lParam)
- {
- switch(wParam)
- {
- case VIEWMSG_SETCTRLWND:
- m_hWndCtrl = (HWND)lParam;
- m_nOrder = static_cast<ORDERINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTORDER));
- SetCurrentPattern(static_cast<PATTERNINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTPATTERN)));
- break;
- case VIEWMSG_GETCURRENTPATTERN:
- return m_nPattern;
- case VIEWMSG_SETCURRENTPATTERN:
- m_nOrder = static_cast<ORDERINDEX>(SendCtrlMessage(CTRLMSG_GETCURRENTORDER));
- SetCurrentPattern(static_cast<PATTERNINDEX>(lParam));
- break;
- case VIEWMSG_GETCURRENTPOS:
- return (m_nPattern << 16) | GetCurrentRow();
- case VIEWMSG_FOLLOWSONG:
- m_Status.reset(psFollowSong);
- if(lParam)
- {
- CModDoc *pModDoc = GetDocument();
- m_Status.set(psFollowSong);
- if(pModDoc)
- pModDoc->SetNotifications(Notification::Position | Notification::VUMeters);
- if(pModDoc)
- pModDoc->SetFollowWnd(m_hWnd);
- SetFocus();
- } else
- {
- InvalidateRow();
- }
- break;
- case VIEWMSG_PATTERNLOOP:
- SendCtrlMessage(CTRLMSG_PAT_LOOP, lParam);
- break;
- case VIEWMSG_SETRECORD:
- m_Status.set(psRecordingEnabled, !!lParam);
- break;
- case VIEWMSG_SETSPACING:
- m_nSpacing = static_cast<UINT>(lParam);
- break;
- case VIEWMSG_PATTERNPROPERTIES:
- ShowPatternProperties(static_cast<PATTERNINDEX>(lParam));
- GetParentFrame()->SetActiveView(this);
- break;
- case VIEWMSG_SETVUMETERS:
- m_Status.set(psShowVUMeters, !!lParam);
- UpdateSizes();
- UpdateScrollSize();
- InvalidatePattern(true, true);
- break;
- case VIEWMSG_SETPLUGINNAMES:
- m_Status.set(psShowPluginNames, !!lParam);
- UpdateSizes();
- UpdateScrollSize();
- InvalidatePattern(true, true);
- break;
- case VIEWMSG_DOMIDISPACING:
- if(m_nSpacing)
- {
- int temp = timeGetTime();
- if(temp - lParam >= 60)
- {
- CModDoc *pModDoc = GetDocument();
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- if(!m_Status[psFollowSong]
- || (pMainFrm->GetFollowSong(pModDoc) != m_hWnd)
- || (pModDoc->GetSoundFile().IsPaused()))
- {
- SetCurrentRow(GetCurrentRow() + m_nSpacing);
- }
- } else
- {
- Sleep(0);
- PostMessage(WM_MOD_VIEWMSG, VIEWMSG_DOMIDISPACING, lParam);
- }
- }
- break;
- case VIEWMSG_LOADSTATE:
- if(lParam)
- {
- PATTERNVIEWSTATE *pState = (PATTERNVIEWSTATE *)lParam;
- if(pState->nDetailLevel != PatternCursor::firstColumn)
- m_nDetailLevel = pState->nDetailLevel;
- if(pState->initialized)
- {
- SetCurrentPattern(pState->nPattern);
- // Fix: Horizontal scrollbar pos screwed when selecting with mouse
- SetCursorPosition(pState->cursor);
- SetCurSel(pState->selection);
- }
- }
- break;
- case VIEWMSG_SAVESTATE:
- if(lParam)
- {
- PATTERNVIEWSTATE *pState = (PATTERNVIEWSTATE *)lParam;
- pState->initialized = true;
- pState->nPattern = m_nPattern;
- pState->cursor = m_Cursor;
- pState->selection = m_Selection;
- pState->nDetailLevel = m_nDetailLevel;
- pState->nOrder = GetCurrentOrder();
- }
- break;
- case VIEWMSG_EXPANDPATTERN:
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc->ExpandPattern(m_nPattern))
- {
- m_Cursor.SetRow(m_Cursor.GetRow() * 2);
- SetCurrentPattern(m_nPattern);
- }
- break;
- }
- case VIEWMSG_SHRINKPATTERN:
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc->ShrinkPattern(m_nPattern))
- {
- m_Cursor.SetRow(m_Cursor.GetRow() / 2);
- SetCurrentPattern(m_nPattern);
- }
- break;
- }
- case VIEWMSG_COPYPATTERN:
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr && pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- CopyPattern(m_nPattern, PatternRect(PatternCursor(0, 0), PatternCursor(pSndFile->Patterns[m_nPattern].GetNumRows() - 1, pSndFile->GetNumChannels() - 1, PatternCursor::lastColumn)));
- }
- break;
- }
- case VIEWMSG_PASTEPATTERN:
- PastePattern(m_nPattern, PatternCursor(0), PatternClipboard::pmOverwrite);
- InvalidatePattern();
- break;
- case VIEWMSG_AMPLIFYPATTERN:
- OnPatternAmplify();
- break;
- case VIEWMSG_SETDETAIL:
- if(lParam != m_nDetailLevel)
- {
- m_nDetailLevel = static_cast<PatternCursor::Columns>(lParam);
- UpdateSizes();
- UpdateScrollSize();
- SetCurrentColumn(m_Cursor);
- InvalidatePattern(true, true);
- }
- break;
- case VIEWMSG_DOSCROLL:
- OnMouseWheel(0, static_cast<short>(lParam), CPoint(0, 0));
- break;
- default:
- return CModScrollView::OnModViewMsg(wParam, lParam);
- }
- return 0;
- }
- void CViewPattern::CursorJump(int distance, bool snap)
- {
- ROWINDEX row = GetCurrentRow();
- const bool upwards = distance < 0;
- const int distanceAbs = std::abs(distance);
- if(snap && distanceAbs)
- // cppcheck false-positive
- // cppcheck-suppress signConversion
- row = (((row + (upwards ? -1 : 0)) / distanceAbs) + (upwards ? 0 : 1)) * distanceAbs;
- else
- row += distance;
- row = SetCurrentRow(row, true);
- if(IsLiveRecord() && !m_Status[psDragActive])
- {
- CriticalSection cs;
- CSoundFile &sndFile = GetDocument()->GetSoundFile();
- if(m_nOrder != sndFile.m_PlayState.m_nCurrentOrder)
- {
- // We jumped to a different order
- sndFile.ResetChannels();
- sndFile.StopAllVsti();
- }
- sndFile.m_PlayState.m_nCurrentOrder = sndFile.m_PlayState.m_nNextOrder = GetCurrentOrder();
- sndFile.m_PlayState.m_nPattern = m_nPattern;
- sndFile.m_PlayState.m_nRow = m_nPlayRow = row;
- sndFile.m_PlayState.m_nNextRow = m_nNextPlayRow = row + 1;
- // Queue the correct follow-up pattern if we just jumped to the last row.
- if(sndFile.Patterns.IsValidPat(m_nPattern) && m_nNextPlayRow >= sndFile.Patterns[m_nPattern].GetNumRows())
- {
- sndFile.m_PlayState.m_nNextOrder++;
- }
- CMainFrame::GetMainFrame()->ResetNotificationBuffer();
- } else
- {
- if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
- {
- PatternStep(row);
- }
- }
- }
- LRESULT CViewPattern::OnCustomKeyMsg(WPARAM wParam, LPARAM lParam)
- {
- CModDoc *pModDoc = GetDocument();
- if(!pModDoc)
- return kcNull;
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- switch(wParam)
- {
- case kcPrevInstrument: OnPrevInstrument(); return wParam;
- case kcNextInstrument: OnNextInstrument(); return wParam;
- case kcPrevOrder: OnPrevOrder(); return wParam;
- case kcNextOrder: OnNextOrder(); return wParam;
- case kcPatternPlayRow: OnPatternStep(); return wParam;
- case kcPatternRecord: OnPatternRecord(); return wParam;
- case kcCursorCopy: OnCursorCopy(); return wParam;
- case kcCursorPaste: OnCursorPaste(); return wParam;
- case kcChannelMute: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
- OnMuteChannel(c);
- return wParam;
- case kcChannelSolo: OnSoloChannel(GetCurrentChannel()); return wParam;
- case kcChannelUnmuteAll: OnUnmuteAll(); return wParam;
- case kcToggleChanMuteOnPatTransition: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
- TogglePendingMute(c);
- return wParam;
- case kcUnmuteAllChnOnPatTransition: OnPendingUnmuteAllChnFromClick(); return wParam;
- case kcChannelRecordSelect: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
- pModDoc->ToggleChannelRecordGroup(c, RecordGroup::Group1);
- InvalidateChannelsHeaders(); return wParam;
- case kcChannelSplitRecordSelect: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
- pModDoc->ToggleChannelRecordGroup(c, RecordGroup::Group2);
- InvalidateChannelsHeaders(); return wParam;
- case kcChannelReset: for(CHANNELINDEX c = m_Selection.GetStartChannel(); c <= m_Selection.GetEndChannel(); c++)
- ResetChannel(m_Cursor.GetChannel());
- return wParam;
- case kcTimeAtRow: OnShowTimeAtRow(); return wParam;
- case kcSoloChnOnPatTransition: PendingSoloChn(GetCurrentChannel()); return wParam;
- case kcTransposeUp: OnTransposeUp(); return wParam;
- case kcTransposeDown: OnTransposeDown(); return wParam;
- case kcTransposeOctUp: OnTransposeOctUp(); return wParam;
- case kcTransposeOctDown: OnTransposeOctDown(); return wParam;
- case kcTransposeCustom: OnTransposeCustom(); return wParam;
- case kcTransposeCustomQuick: OnTransposeCustomQuick(); return wParam;
- case kcDataEntryUp: DataEntry(true, false); return wParam;
- case kcDataEntryDown: DataEntry(false, false); return wParam;
- case kcDataEntryUpCoarse: DataEntry(true, true); return wParam;
- case kcDataEntryDownCoarse: DataEntry(false, true); return wParam;
- case kcSelectChannel: OnSelectCurrentChannel(); return wParam;
- case kcSelectColumn: OnSelectCurrentColumn(); return wParam;
- case kcPatternAmplify: OnPatternAmplify(); return wParam;
- case kcPatternSetInstrumentNotEmpty:
- case kcPatternSetInstrument: SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(GetCurrentInstrument()), wParam == kcPatternSetInstrument); return wParam;
- case kcPatternInterpolateNote: OnInterpolateNote(); return wParam;
- case kcPatternInterpolateInstr: OnInterpolateInstr(); return wParam;
- case kcPatternInterpolateVol: OnInterpolateVolume(); return wParam;
- case kcPatternInterpolateEffect: OnInterpolateEffect(); return wParam;
- case kcPatternVisualizeEffect: OnVisualizeEffect(); return wParam;
- //case kcPatternOpenRandomizer: OnOpenRandomizer(); return wParam;
- case kcPatternGrowSelection: OnGrowSelection(); return wParam;
- case kcPatternShrinkSelection: OnShrinkSelection(); return wParam;
- // Pattern navigation:
- case kcPatternJumpUph1Select:
- case kcPatternJumpUph1: CursorJump(-(int)GetRowsPerMeasure(), false); return wParam;
- case kcPatternJumpDownh1Select:
- case kcPatternJumpDownh1: CursorJump(GetRowsPerMeasure(), false); return wParam;
- case kcPatternJumpUph2Select:
- case kcPatternJumpUph2: CursorJump(-(int)GetRowsPerBeat(), false); return wParam;
- case kcPatternJumpDownh2Select:
- case kcPatternJumpDownh2: CursorJump(GetRowsPerBeat(), false); return wParam;
- case kcPatternSnapUph1Select:
- case kcPatternSnapUph1: CursorJump(-(int)GetRowsPerMeasure(), true); return wParam;
- case kcPatternSnapDownh1Select:
- case kcPatternSnapDownh1: CursorJump(GetRowsPerMeasure(), true); return wParam;
- case kcPatternSnapUph2Select:
- case kcPatternSnapUph2: CursorJump(-(int)GetRowsPerBeat(), true); return wParam;
- case kcPatternSnapDownh2Select:
- case kcPatternSnapDownh2: CursorJump(GetRowsPerBeat(), true); return wParam;
- case kcNavigateDownSelect:
- case kcNavigateDown: CursorJump(1, false); return wParam;
- case kcNavigateUpSelect:
- case kcNavigateUp: CursorJump(-1, false); return wParam;
- case kcNavigateDownBySpacingSelect:
- case kcNavigateDownBySpacing: CursorJump(m_nSpacing, false); return wParam;
- case kcNavigateUpBySpacingSelect:
- case kcNavigateUpBySpacing: CursorJump(-(int)m_nSpacing, false); return wParam;
- case kcNavigateLeftSelect:
- case kcNavigateLeft:
- MoveCursor(false);
- return wParam;
- case kcNavigateRightSelect:
- case kcNavigateRight:
- MoveCursor(true);
- return wParam;
- case kcNavigateNextChanSelect:
- case kcNavigateNextChan: SetCurrentColumn((GetCurrentChannel() + 1) % sndFile.GetNumChannels(), m_Cursor.GetColumnType()); return wParam;
- case kcNavigatePrevChanSelect:
- case kcNavigatePrevChan:{if(GetCurrentChannel() > 0)
- SetCurrentColumn((GetCurrentChannel() - 1) % sndFile.GetNumChannels(), m_Cursor.GetColumnType());
- else
- SetCurrentColumn(sndFile.GetNumChannels() - 1, m_Cursor.GetColumnType());
- SetSelToCursor();
- return wParam;}
- case kcHomeHorizontalSelect:
- case kcHomeHorizontal: if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
- else if (GetCurrentRow() > 0) SetCurrentRow(0);
- return wParam;
- case kcHomeVerticalSelect:
- case kcHomeVertical: if (GetCurrentRow() > 0) SetCurrentRow(0);
- else if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
- return wParam;
- case kcHomeAbsoluteSelect:
- case kcHomeAbsolute: if (!m_Cursor.IsInFirstColumn()) SetCurrentColumn(0);
- if (GetCurrentRow() > 0) SetCurrentRow(0);
- return wParam;
- case kcEndHorizontalSelect:
- case kcEndHorizontal: if (m_Cursor.CompareColumn(PatternCursor(0, sndFile.GetNumChannels() - 1, m_nDetailLevel)) < 0) SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
- else if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
- return wParam;
- case kcEndVerticalSelect:
- case kcEndVertical: if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
- else if (m_Cursor.CompareColumn(PatternCursor(0, sndFile.GetNumChannels() - 1, m_nDetailLevel)) < 0) SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
- return wParam;
- case kcEndAbsoluteSelect:
- case kcEndAbsolute: SetCurrentColumn(sndFile.GetNumChannels() - 1, m_nDetailLevel);
- if (GetCurrentRow() < pModDoc->GetPatternSize(m_nPattern) - 1) SetCurrentRow(pModDoc->GetPatternSize(m_nPattern) - 1);
- return wParam;
- case kcPrevEntryInColumn:
- case kcNextEntryInColumn:
- JumpToPrevOrNextEntry(wParam == kcNextEntryInColumn, false);
- return wParam;
- case kcPrevEntryInColumnSelect:
- case kcNextEntryInColumnSelect:
- JumpToPrevOrNextEntry(wParam == kcNextEntryInColumnSelect, true);
- return wParam;
- case kcNextPattern: { PATTERNINDEX n = m_nPattern + 1;
- while ((n < sndFile.Patterns.Size()) && !sndFile.Patterns.IsValidPat(n)) n++;
- SetCurrentPattern((n < sndFile.Patterns.Size()) ? n : 0);
- ORDERINDEX currentOrder = GetCurrentOrder();
- ORDERINDEX newOrder = Order().FindOrder(m_nPattern, currentOrder, true);
- if(newOrder != ORDERINDEX_INVALID)
- SetCurrentOrder(newOrder);
- return wParam;
- }
- case kcPrevPattern: { PATTERNINDEX n = (m_nPattern) ? m_nPattern - 1 : sndFile.Patterns.Size() - 1;
- while (n > 0 && !sndFile.Patterns.IsValidPat(n)) n--;
- SetCurrentPattern(n);
- ORDERINDEX currentOrder = GetCurrentOrder();
- ORDERINDEX newOrder = Order().FindOrder(m_nPattern, currentOrder, false);
- if(newOrder != ORDERINDEX_INVALID)
- SetCurrentOrder(newOrder);
- return wParam;
- }
- case kcPrevSequence:
- case kcNextSequence:
- SendCtrlMessage(CTRLMSG_PAT_SETSEQUENCE, mpt::wrapping_modulo(sndFile.Order.GetCurrentSequenceIndex() + (wParam == kcPrevSequence ? -1 : 1), sndFile.Order.GetNumSequences()));
- return wParam;
- case kcSelectWithCopySelect:
- case kcSelectWithNav:
- case kcSelect: if(!m_Status[psDragnDropEdit | psRowSelection | psChannelSelection | psMouseDragSelect]) m_StartSel = m_Cursor;
- m_Status.set(psKeyboardDragSelect);
- return wParam;
- case kcSelectOffWithCopySelect:
- case kcSelectOffWithNav:
- case kcSelectOff: m_Status.reset(psKeyboardDragSelect | psShiftSelect);
- return wParam;
- case kcCopySelectWithSelect:
- case kcCopySelectWithNav:
- case kcCopySelect: if(!m_Status[psDragnDropEdit | psRowSelection | psChannelSelection | psMouseDragSelect]) m_StartSel = m_Cursor;
- m_Status.set(psCtrlDragSelect); return wParam;
- case kcCopySelectOffWithSelect:
- case kcCopySelectOffWithNav:
- case kcCopySelectOff: m_Status.reset(psCtrlDragSelect); return wParam;
- case kcSelectBeat:
- case kcSelectMeasure:
- SelectBeatOrMeasure(wParam == kcSelectBeat); return wParam;
- case kcSelectEvent: SetCurSel(PatternCursor(m_Selection.GetStartRow(), m_Selection.GetStartChannel(), PatternCursor::firstColumn),
- PatternCursor(m_Selection.GetEndRow(), m_Selection.GetEndChannel(), PatternCursor::lastColumn));
- return wParam;
- case kcSelectRow: SetCurSel(PatternCursor(m_Selection.GetStartRow(), 0, PatternCursor::firstColumn),
- PatternCursor(m_Selection.GetEndRow(), sndFile.GetNumChannels(), PatternCursor::lastColumn));
- return wParam;
- case kcClearRow: OnClearField(RowMask(), false); return wParam;
- case kcClearField: OnClearField(RowMask(m_Cursor), false); return wParam;
- case kcClearFieldITStyle: OnClearField(RowMask(m_Cursor), false, true); return wParam;
- case kcClearRowStep: OnClearField(RowMask(), true); return wParam;
- case kcClearFieldStep: OnClearField(RowMask(m_Cursor), true); return wParam;
- case kcClearFieldStepITStyle: OnClearField(RowMask(m_Cursor), true, true); return wParam;
- case kcDeleteRow: OnDeleteRow(); return wParam;
- case kcDeleteWholeRow: OnDeleteWholeRow(); return wParam;
- case kcDeleteRowGlobal: OnDeleteRowGlobal(); return wParam;
- case kcDeleteWholeRowGlobal: OnDeleteWholeRowGlobal(); return wParam;
- case kcInsertRow: OnInsertRow(); return wParam;
- case kcInsertWholeRow: OnInsertWholeRow(); return wParam;
- case kcInsertRowGlobal: OnInsertRowGlobal(); return wParam;
- case kcInsertWholeRowGlobal: OnInsertWholeRowGlobal(); return wParam;
- case kcShowNoteProperties: ShowEditWindow(); return wParam;
- case kcShowPatternProperties: OnPatternProperties(); return wParam;
- case kcShowSplitKeyboardSettings: SetSplitKeyboardSettings(); return wParam;
- case kcShowEditMenu:
- {
- CPoint pt = GetPointFromPosition(m_Cursor);
- pt.x += GetChannelWidth() / 2;
- pt.y += GetRowHeight() / 2;
- OnRButtonDown(0, pt);
- }
- return wParam;
- case kcShowChannelCtxMenu:
- {
- CPoint pt = GetPointFromPosition(m_Cursor);
- pt.x += GetChannelWidth() / 2;
- pt.y = (m_szHeader.cy - m_szPluginHeader.cy) / 2;
- OnRButtonDown(0, pt);
- }
- return wParam;
- case kcShowChannelPluginCtxMenu:
- {
- CPoint pt = GetPointFromPosition(m_Cursor);
- pt.x += GetChannelWidth() / 2;
- pt.y = m_szHeader.cy - m_szPluginHeader.cy / 2;
- OnRButtonDown(0, pt);
- }
- return wParam;
- case kcPatternGoto: OnEditGoto(); return wParam;
- case kcNoteCut: TempEnterNote(NOTE_NOTECUT); return wParam;
- case kcNoteOff: TempEnterNote(NOTE_KEYOFF); return wParam;
- case kcNoteFade: TempEnterNote(NOTE_FADE); return wParam;
- case kcNotePC: TempEnterNote(NOTE_PC); return wParam;
- case kcNotePCS: TempEnterNote(NOTE_PCS); return wParam;
- case kcEditUndo: OnEditUndo(); return wParam;
- case kcEditRedo: OnEditRedo(); return wParam;
- case kcEditFind: OnEditFind(); return wParam;
- case kcEditFindNext: OnEditFindNext(); return wParam;
- case kcEditCut: OnEditCut(); return wParam;
- case kcEditCopy: OnEditCopy(); return wParam;
- case kcCopyAndLoseSelection:
- OnEditCopy();
- [[fallthrough]];
- case kcLoseSelection:
- SetSelToCursor();
- return wParam;
- case kcEditPaste: OnEditPaste(); return wParam;
- case kcEditMixPaste: OnEditMixPaste(); return wParam;
- case kcEditMixPasteITStyle: OnEditMixPasteITStyle(); return wParam;
- case kcEditPasteFlood: OnEditPasteFlood(); return wParam;
- case kcEditPushForwardPaste: OnEditPushForwardPaste(); return wParam;
- case kcEditSelectAll: OnEditSelectAll(); return wParam;
- case kcTogglePluginEditor: TogglePluginEditor(GetCurrentChannel()); return wParam;
- case kcToggleFollowSong: SendCtrlMessage(CTRLMSG_PAT_FOLLOWSONG, 1); return wParam;
- case kcChangeLoopStatus: SendCtrlMessage(CTRLMSG_PAT_LOOP, -1); return wParam;
- case kcNewPattern: SendCtrlMessage(CTRLMSG_PAT_NEWPATTERN); return wParam;
- case kcDuplicatePattern: SendCtrlMessage(CTRLMSG_PAT_DUPPATTERN); return wParam;
- case kcSwitchToOrderList: OnSwitchToOrderList(); return wParam;
- case kcToggleOverflowPaste: TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_OVERFLOWPASTE; return wParam;
- case kcToggleNoteOffRecordPC: TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_KBDNOTEOFF; return wParam;
- case kcToggleNoteOffRecordMIDI: TrackerSettings::Instance().m_dwMidiSetup ^= MIDISETUP_RECORDNOTEOFF; return wParam;
- case kcPatternEditPCNotePlugin: OnTogglePCNotePluginEditor(); return wParam;
- case kcQuantizeSettings: OnSetQuantize(); return wParam;
- case kcLockPlaybackToRows: OnLockPatternRows(); return wParam;
- case kcFindInstrument: FindInstrument(); return wParam;
- case kcChannelSettings:
- {
- // Open centered Quick Channel Settings dialog.
- CRect windowPos;
- GetWindowRect(windowPos);
- m_quickChannelProperties.Show(GetDocument(), m_Cursor.GetChannel(), CPoint(windowPos.left + windowPos.Width() / 2, windowPos.top + windowPos.Height() / 2));
- return wParam;
- }
- case kcChannelTranspose: m_MenuCursor = m_Cursor; OnTransposeChannel(); return wParam;
- case kcChannelDuplicate: m_MenuCursor = m_Cursor; OnDuplicateChannel(); return wParam;
- case kcChannelAddBefore: m_MenuCursor = m_Cursor; OnAddChannelFront(); return wParam;
- case kcChannelAddAfter: m_MenuCursor = m_Cursor; OnAddChannelAfter(); return wParam;
- case kcChannelRemove: m_MenuCursor = m_Cursor; OnRemoveChannel(); return wParam;
- case kcChannelMoveLeft:
- if(CHANNELINDEX chn = m_Selection.GetStartChannel(); chn > 0)
- DragChannel(chn, chn - 1u, m_Selection.GetNumChannels(), false);
- return wParam;
- case kcChannelMoveRight:
- if (CHANNELINDEX chn = m_Selection.GetStartChannel(); chn < sndFile.GetNumChannels() - m_Selection.GetNumChannels())
- DragChannel(chn, chn + 1u, m_Selection.GetNumChannels(), false);
- return wParam;
- case kcSplitPattern: m_MenuCursor = m_Cursor; OnSplitPattern(); return wParam;
- case kcDecreaseSpacing:
- if(m_nSpacing > 0) SetSpacing(m_nSpacing - 1);
- return wParam;
- case kcIncreaseSpacing:
- if(m_nSpacing < MAX_SPACING) SetSpacing(m_nSpacing + 1);
- return wParam;
- case kcChordEditor:
- {
- CChordEditor dlg(this);
- dlg.DoModal();
- return wParam;
- }
- // Clipboard Manager
- case kcToggleClipboardManager:
- PatternClipboardDialog::Toggle();
- return wParam;
- case kcClipboardPrev:
- PatternClipboard::CycleBackward();
- PatternClipboardDialog::UpdateList();
- return wParam;
- case kcClipboardNext:
- PatternClipboard::CycleForward();
- PatternClipboardDialog::UpdateList();
- return wParam;
- case kcCutPatternChannel:
- PatternClipboard::Copy(sndFile, GetCurrentPattern(), GetCurrentChannel());
- OnEditSelectChannel();
- OnClearSelection(false);
- return wParam;
- case kcCutPattern:
- PatternClipboard::Copy(sndFile, GetCurrentPattern());
- OnEditSelectAll();
- OnClearSelection(false);
- return wParam;
- case kcCopyPatternChannel:
- PatternClipboard::Copy(sndFile, GetCurrentPattern(), GetCurrentChannel());
- return wParam;
- case kcCopyPattern:
- PatternClipboard::Copy(sndFile, GetCurrentPattern());
- return wParam;
- case kcPastePatternChannel:
- case kcPastePattern:
- if(PatternClipboard::Paste(sndFile, GetCurrentPattern(), wParam == kcPastePatternChannel ? GetCurrentChannel() : CHANNELINDEX_INVALID))
- {
- SetModified();
- InvalidatePattern();
- GetDocument()->UpdateAllViews(this, PatternHint(GetCurrentPattern()).Data(), this);
- }
- return wParam;
- case kcTogglePatternPlayRow:
- TrackerSettings::Instance().m_dwPatternSetup ^= PATTERN_PLAYNAVIGATEROW;
- CMainFrame::GetMainFrame()->SetHelpText((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
- ? _T("Play whole row when navigatin was turned is now enabled.") : _T("Play whole row when navigatin was turned is now disabled."));
- return wParam;
- }
- // 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)
- const auto keyCombination = KeyCombination::FromLPARAM(lParam);
- const bool enterNote = keyCombination.EventType() != kKeyEventRepeat || (IsEditingEnabled() && m_nSpacing != 0);
- // Ranges:
- if(wParam >= kcVPStartNotes && wParam <= kcVPEndNotes)
- {
- if(enterNote)
- TempEnterNote(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartNotes)));
- return wParam;
- } else if(wParam >= kcVPStartChords && wParam <= kcVPEndChords)
- {
- if(enterNote)
- TempEnterChord(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartChords)));
- return wParam;
- }
- if(wParam >= kcVPStartNoteStops && wParam <= kcVPEndNoteStops)
- {
- TempStopNote(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartNoteStops)));
- return wParam;
- } else if(wParam >= kcVPStartChordStops && wParam <= kcVPEndChordStops)
- {
- TempStopChord(GetNoteWithBaseOctave(static_cast<int>(wParam - kcVPStartChordStops)));
- return wParam;
- }
- if(wParam >= kcSetSpacing0 && wParam <= kcSetSpacing9)
- {
- SetSpacing(static_cast<int>(wParam) - kcSetSpacing0);
- return wParam;
- }
- if(wParam >= kcSetIns0 && wParam <= kcSetIns9)
- {
- if(IsEditingEnabled_bmsg())
- TempEnterIns(static_cast<int>(wParam) - kcSetIns0);
- return wParam;
- }
- if(wParam >= kcSetOctave0 && wParam <= kcSetOctave9)
- {
- if(IsEditingEnabled_bmsg())
- TempEnterOctave(static_cast<int>(wParam) - kcSetOctave0);
- return wParam;
- }
- if(wParam >= kcSetOctaveStop0 && wParam <= kcSetOctaveStop9)
- {
- TempStopOctave(static_cast<int>(wParam) - kcSetOctaveStop0);
- return wParam;
- }
- if(wParam >= kcSetVolumeStart && wParam <= kcSetVolumeEnd)
- {
- if(IsEditingEnabled_bmsg())
- TempEnterVol(static_cast<int>(wParam) - kcSetVolumeStart);
- return wParam;
- }
- if(wParam >= kcSetFXStart && wParam <= kcSetFXEnd)
- {
- if(IsEditingEnabled_bmsg())
- TempEnterFX(static_cast<ModCommand::COMMAND>(wParam - kcSetFXStart + 1));
- return wParam;
- }
- if(wParam >= kcSetFXParam0 && wParam <= kcSetFXParamF)
- {
- if(IsEditingEnabled_bmsg())
- TempEnterFXparam(static_cast<int>(wParam) - kcSetFXParam0);
- return wParam;
- }
- return kcNull;
- }
- // Move pattern cursor to left or right, respecting invisible columns.
- void CViewPattern::MoveCursor(bool moveRight)
- {
- if(!moveRight)
- {
- // Move cursor one column to the left
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP) && m_Cursor.IsInFirstColumn())
- {
- // Wrap around to last channel
- SetCurrentColumn(GetDocument()->GetNumChannels() - 1, m_nDetailLevel);
- } else if(!m_Cursor.IsInFirstColumn())
- {
- m_Cursor.Move(0, 0, -1);
- SetCurrentColumn(m_Cursor);
- }
- } else
- {
- // Move cursor one column to the right
- const PatternCursor rightmost(0, GetDocument()->GetNumChannels() - 1, m_nDetailLevel);
- if(m_Cursor.CompareColumn(rightmost) >= 0)
- {
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_WRAP))
- {
- // Wrap around to first channel.
- SetCurrentColumn(0);
- } else
- {
- SetCurrentColumn(rightmost);
- }
- } else
- {
- do
- {
- m_Cursor.Move(0, 0, 1);
- } while(m_Cursor.GetColumnType() > m_nDetailLevel);
- SetCurrentColumn(m_Cursor);
- }
- }
- }
- static bool EnterPCNoteValue(int v, ModCommand &m, uint16 (ModCommand::*getMethod)() const, void (ModCommand::*setMethod)(uint16))
- {
- if(v < 0 || v > 9)
- return false;
- uint16 val = (m.*getMethod)();
- // Move existing digits to left, drop out leftmost digit and push new digit to the least significant digit.
- val = static_cast<uint16>((val % 100) * 10 + v);
- LimitMax(val, static_cast<uint16>(ModCommand::maxColumnValue));
- (m.*setMethod)(val);
- return true;
- }
- // Enter volume effect / number in the pattern.
- void CViewPattern::TempEnterVol(int v)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
- return;
- PrepareUndo(m_Cursor, m_Cursor, "Volume Entry");
- ModCommand &target = GetCursorCommand();
- ModCommand oldcmd = target; // This is the command we are about to overwrite
- const bool isDigit = (v >= 0) && (v <= 9);
- if(target.IsPcNote())
- {
- if(EnterPCNoteValue(v, target, &ModCommand::GetValueVolCol, &ModCommand::SetValueVolCol))
- m_PCNoteEditMemory = target;
- } else
- {
- ModCommand::VOLCMD volcmd = target.volcmd;
- uint16 vol = target.vol;
- if(isDigit)
- {
- vol = ((vol * 10) + v) % 100;
- if(!volcmd)
- volcmd = VOLCMD_VOLUME;
- } else
- {
- switch(v + kcSetVolumeStart)
- {
- case kcSetVolumeVol: volcmd = VOLCMD_VOLUME; break;
- case kcSetVolumePan: volcmd = VOLCMD_PANNING; break;
- case kcSetVolumeVolSlideUp: volcmd = VOLCMD_VOLSLIDEUP; break;
- case kcSetVolumeVolSlideDown: volcmd = VOLCMD_VOLSLIDEDOWN; break;
- case kcSetVolumeFineVolUp: volcmd = VOLCMD_FINEVOLUP; break;
- case kcSetVolumeFineVolDown: volcmd = VOLCMD_FINEVOLDOWN; break;
- case kcSetVolumeVibratoSpd: volcmd = VOLCMD_VIBRATOSPEED; break;
- case kcSetVolumeVibrato: volcmd = VOLCMD_VIBRATODEPTH; break;
- case kcSetVolumeXMPanLeft: volcmd = VOLCMD_PANSLIDELEFT; break;
- case kcSetVolumeXMPanRight: volcmd = VOLCMD_PANSLIDERIGHT; break;
- case kcSetVolumePortamento: volcmd = VOLCMD_TONEPORTAMENTO; break;
- case kcSetVolumeITPortaUp: volcmd = VOLCMD_PORTAUP; break;
- case kcSetVolumeITPortaDown: volcmd = VOLCMD_PORTADOWN; break;
- case kcSetVolumeITOffset: volcmd = VOLCMD_OFFSET; break;
- }
- if(target.volcmd == VOLCMD_NONE && volcmd == m_cmdOld.volcmd)
- {
- vol = m_cmdOld.vol;
- }
- }
- uint16 max;
- switch(volcmd)
- {
- case VOLCMD_VOLUME:
- case VOLCMD_PANNING:
- max = 64;
- break;
- default:
- max = (pSndFile->GetType() == MOD_TYPE_XM) ? 0x0F : 9;
- break;
- }
- if(vol > max)
- vol %= 10;
- if(pSndFile->GetModSpecifications().HasVolCommand(volcmd))
- {
- m_cmdOld.volcmd = target.volcmd = volcmd;
- m_cmdOld.vol = target.vol = static_cast<ModCommand::VOL>(vol);
- }
- }
- SetSelToCursor();
- if(oldcmd != target)
- {
- SetModified(false);
- InvalidateCell(m_Cursor);
- UpdateIndicator();
- }
- // Cursor step for command letter
- if(!target.IsPcNote() && !isDigit && m_nSpacing > 0 && !IsLiveRecord() && TrackerSettings::Instance().patternStepCommands)
- {
- if(m_Cursor.GetRow() + m_nSpacing < pSndFile->Patterns[m_nPattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
- {
- SetCurrentRow(m_Cursor.GetRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
- }
- }
- }
- void CViewPattern::SetSpacing(int n)
- {
- if(static_cast<UINT>(n) != m_nSpacing)
- {
- m_nSpacing = static_cast<UINT>(n);
- PostCtrlMessage(CTRLMSG_SETSPACING, m_nSpacing);
- }
- }
- // Enter an effect letter in the pattern
- void CViewPattern::TempEnterFX(ModCommand::COMMAND c, int v)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
- {
- return;
- }
- ModCommand &target = GetCursorCommand();
- ModCommand oldcmd = target; // This is the command we are about to overwrite
- PrepareUndo(m_Cursor, m_Cursor, "Effect Entry");
- if(target.IsPcNote())
- {
- if(EnterPCNoteValue(c, target, &ModCommand::GetValueEffectCol, &ModCommand::SetValueEffectCol))
- m_PCNoteEditMemory = target;
- } else if(pSndFile->GetModSpecifications().HasCommand(c))
- {
- if(c != CMD_NONE)
- {
- if((c == m_cmdOld.command) && (!target.param) && (target.command == CMD_NONE))
- {
- target.param = m_cmdOld.param;
- } else
- {
- m_cmdOld.param = 0;
- }
- m_cmdOld.command = c;
- }
- target.command = c;
- if(v >= 0)
- {
- target.param = static_cast<ModCommand::PARAM>(v);
- }
- // Check for MOD/XM Speed/Tempo command
- if((pSndFile->GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))
- && (target.command == CMD_SPEED || target.command == CMD_TEMPO))
- {
- target.command = static_cast<ModCommand::COMMAND>((target.param <= pSndFile->GetModSpecifications().speedMax) ? CMD_SPEED : CMD_TEMPO);
- }
- }
- SetSelToCursor();
- if(oldcmd != target)
- {
- SetModified(false);
- InvalidateCell(m_Cursor);
- UpdateIndicator();
- }
- // Cursor step for command letter
- if(!target.IsPcNote() && m_nSpacing > 0 && !IsLiveRecord() && TrackerSettings::Instance().patternStepCommands)
- {
- if(m_Cursor.GetRow() + m_nSpacing < pSndFile->Patterns[m_nPattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
- {
- SetCurrentRow(m_Cursor.GetRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
- }
- }
- }
- // Enter an effect param in the pattenr
- void CViewPattern::TempEnterFXparam(int v)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
- {
- return;
- }
- ModCommand &target = GetCursorCommand();
- ModCommand oldcmd = target; // This is the command we are about to overwrite
- PrepareUndo(m_Cursor, m_Cursor, "Parameter Entry");
- if(target.IsPcNote())
- {
- if(EnterPCNoteValue(v, target, &ModCommand::GetValueEffectCol, &ModCommand::SetValueEffectCol))
- m_PCNoteEditMemory = target;
- } else
- {
- target.param = static_cast<ModCommand::PARAM>((target.param << 4) | v);
- if(target.command == m_cmdOld.command)
- {
- m_cmdOld.param = target.param;
- }
- // Check for MOD/XM Speed/Tempo command
- if((pSndFile->GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))
- && (target.command == CMD_SPEED || target.command == CMD_TEMPO))
- {
- target.command = static_cast<ModCommand::COMMAND>((target.param <= pSndFile->GetModSpecifications().speedMax) ? CMD_SPEED : CMD_TEMPO);
- }
- }
- SetSelToCursor();
- if(target != oldcmd)
- {
- SetModified(false);
- InvalidateCell(m_Cursor);
- UpdateIndicator();
- }
- }
- // Stop a note that has been entered
- void CViewPattern::TempStopNote(ModCommand::NOTE note, const bool fromMidi, bool chordMode)
- {
- CModDoc *pModDoc = GetDocument();
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- if(pModDoc == nullptr || pMainFrm == nullptr || !ModCommand::IsNote(note))
- {
- return;
- }
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- const CModSpecifications &specs = sndFile.GetModSpecifications();
- Limit(note, specs.noteMin, specs.noteMax);
- const bool liveRecord = IsLiveRecord();
- const bool isSplit = IsNoteSplit(note);
- UINT ins = 0;
- chordMode = chordMode && (m_prevChordNote != NOTE_NONE);
- auto &activeNoteMap = isSplit ? m_splitActiveNoteChannel : m_activeNoteChannel;
- const CHANNELINDEX nChnCursor = GetCurrentChannel();
- const CHANNELINDEX nChn = chordMode ? m_chordPatternChannels[0] : (activeNoteMap[note] < sndFile.GetNumChannels() ? activeNoteMap[note] : nChnCursor);
- CHANNELINDEX noteChannels[MPTChord::notesPerChord] = {nChn};
- ModCommand::NOTE notes[MPTChord::notesPerChord] = {note};
- int numNotes = 1;
- if(pModDoc)
- {
- if(isSplit)
- {
- ins = pModDoc->GetSplitKeyboardSettings().splitInstrument;
- if(pModDoc->GetSplitKeyboardSettings().octaveLink)
- {
- int trNote = note + 12 * pModDoc->GetSplitKeyboardSettings().octaveModifier;
- Limit(trNote, specs.noteMin, specs.noteMax);
- note = static_cast<ModCommand::NOTE>(trNote);
- }
- }
- if(!ins)
- ins = GetCurrentInstrument();
- if(!ins)
- ins = m_fallbackInstrument;
- if(chordMode)
- {
- m_Status.reset(psChordPlaying);
- numNotes = ConstructChord(note, notes, m_prevChordBaseNote);
- if(!numNotes)
- {
- return;
- }
- for(int i = 0; i < numNotes; i++)
- {
- pModDoc->NoteOff(notes[i], true, static_cast<INSTRUMENTINDEX>(ins), m_noteChannel[notes[i] - NOTE_MIN]);
- m_noteChannel[notes[i] - NOTE_MIN] = CHANNELINDEX_INVALID;
- m_baPlayingNote.reset(notes[i]);
- noteChannels[i] = m_chordPatternChannels[i];
- }
- m_prevChordNote = NOTE_NONE;
- } else
- {
- m_baPlayingNote.reset(note);
- pModDoc->NoteOff(note, ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_NOTEFADE) || sndFile.GetNumInstruments() == 0), static_cast<INSTRUMENTINDEX>(ins), m_noteChannel[note - NOTE_MIN]);
- m_noteChannel[note - NOTE_MIN] = CHANNELINDEX_INVALID;
- }
- }
- // Enter note off in pattern?
- if(!ModCommand::IsNote(note))
- return;
- if(m_Cursor.GetColumnType() > PatternCursor::instrColumn && (chordMode || !fromMidi))
- return;
- if(!pModDoc || !pMainFrm || !(IsEditingEnabled()))
- return;
- activeNoteMap[note] = NOTE_CHANNEL_MAP_INVALID; //unlock channel
- if(!((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_KBDNOTEOFF) || fromMidi))
- {
- // We don't want to write the note-off into the pattern if this feature is disabled and we're not recording from MIDI.
- return;
- }
- // -- write sdx if playing live
- const bool usePlaybackPosition = (!chordMode) && (liveRecord && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_AUTODELAY));
- //Work out where to put the note off
- PatternEditPos editPos = GetEditPos(sndFile, usePlaybackPosition);
- const bool doQuantize = (liveRecord || (fromMidi && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))) && TrackerSettings::Instance().recordQuantizeRows != 0;
- if(doQuantize)
- {
- QuantizeRow(editPos.pattern, editPos.row);
- }
- ModCommand *pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
- // Don't overwrite:
- if(pTarget->note != NOTE_NONE || pTarget->instr || pTarget->volcmd != VOLCMD_NONE)
- {
- // If there's a note in the current location and the song is playing and following,
- // the user probably just tapped the key - let's try the next row down.
- editPos.row++;
- if(pTarget->note == note && liveRecord && sndFile.Patterns[editPos.pattern].IsValidRow(editPos.row))
- {
- pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
- if(pTarget->note != NOTE_NONE || (!chordMode && (pTarget->instr || pTarget->volcmd)))
- return;
- } else
- {
- return;
- }
- }
- bool modified = false;
- for(int i = 0; i < numNotes; i++)
- {
- if(m_previousNote[noteChannels[i]] != notes[i])
- {
- // This might be a note-off from a past note, but since we already hit a new note on this channel, we ignore it.
- continue;
- }
- if(!modified)
- {
- pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, nChn, editPos.row, noteChannels[numNotes - 1] - nChn + 1, 1, "Note Stop Entry");
- modified = true;
- }
- pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, noteChannels[i]);
- // -- write sdx if playing live
- if(usePlaybackPosition && m_nPlayTick && pTarget->command == CMD_NONE && !doQuantize)
- {
- pTarget->command = CMD_S3MCMDEX;
- if(!specs.HasCommand(CMD_S3MCMDEX))
- pTarget->command = CMD_MODCMDEX;
- pTarget->param = static_cast<ModCommand::PARAM>(0xD0 | std::min(uint8(0xF), mpt::saturate_cast<uint8>(m_nPlayTick)));
- }
- //Enter note off
- if(sndFile.GetModSpecifications().hasNoteOff && (sndFile.GetNumInstruments() > 0 || !sndFile.GetModSpecifications().hasNoteCut))
- {
- // ===
- // Not used in sample (if module format supports ^^^ instead)
- pTarget->note = NOTE_KEYOFF;
- } else if(sndFile.GetModSpecifications().hasNoteCut)
- {
- // ^^^
- pTarget->note = NOTE_NOTECUT;
- } else
- {
- // we don't have anything to cut (MOD format) - use volume or ECx
- if(usePlaybackPosition && m_nPlayTick && !doQuantize) // ECx
- {
- pTarget->command = CMD_S3MCMDEX;
- if(!specs.HasCommand(CMD_S3MCMDEX))
- pTarget->command = CMD_MODCMDEX;
- pTarget->param = static_cast<ModCommand::PARAM>(0xC0 | std::min(uint8(0xF), mpt::saturate_cast<uint8>(m_nPlayTick)));
- } else // C00
- {
- pTarget->note = NOTE_NONE;
- pTarget->command = CMD_VOLUME;
- pTarget->param = 0;
- }
- }
- pTarget->instr = 0; // Instrument numbers next to note-offs can do all kinds of weird things in XM files, and they are pointless anyway.
- pTarget->volcmd = VOLCMD_NONE;
- pTarget->vol = 0;
- }
- if(!modified)
- return;
- SetModified(false);
- if(editPos.pattern == m_nPattern)
- {
- InvalidateRow(editPos.row);
- } else
- {
- InvalidatePattern();
- }
- // Update only if not recording live.
- if(!liveRecord)
- {
- UpdateIndicator();
- }
- return;
- }
- // Enter an octave number in the pattern
- void CViewPattern::TempEnterOctave(int val)
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr)
- {
- return;
- }
- const ModCommand &target = GetCursorCommand();
- if(target.IsNote())
- {
- int groupSize = GetDocument()->GetInstrumentGroupSize(target.instr);
- // The following might look a bit convoluted... This is mostly because the "middle-C" in
- // custom tunings always has octave 5, no matter how many octaves the tuning actually has.
- int note = mpt::wrapping_modulo(target.note - NOTE_MIDDLEC, groupSize) + (val - 5) * groupSize + NOTE_MIDDLEC;
- Limit(note, NOTE_MIN, NOTE_MAX);
- TempEnterNote(static_cast<ModCommand::NOTE>(note));
- // Memorize note for key-up
- ASSERT(size_t(val) < m_octaveKeyMemory.size());
- m_octaveKeyMemory[val] = target.note;
- }
- }
- // Stop note that has been triggered by entering an octave in the pattern.
- void CViewPattern::TempStopOctave(int val)
- {
- ASSERT(size_t(val) < m_octaveKeyMemory.size());
- if(m_octaveKeyMemory[val] != NOTE_NONE)
- {
- TempStopNote(m_octaveKeyMemory[val]);
- m_octaveKeyMemory[val] = NOTE_NONE;
- }
- }
- // Enter an instrument number in the pattern
- void CViewPattern::TempEnterIns(int val)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !IsEditingEnabled_bmsg())
- {
- return;
- }
- PrepareUndo(m_Cursor, m_Cursor, "Instrument Entry");
- ModCommand &target = GetCursorCommand();
- ModCommand oldcmd = target; // This is the command we are about to overwrite
- UINT instr = target.instr, nTotalMax, nTempMax;
- if(target.IsPcNote()) // this is a plugin index
- {
- nTotalMax = MAX_MIXPLUGINS + 1;
- nTempMax = MAX_MIXPLUGINS + 1;
- } else if(pSndFile->GetNumInstruments() > 0) // this is an instrument index
- {
- nTotalMax = MAX_INSTRUMENTS;
- nTempMax = pSndFile->GetNumInstruments();
- } else
- {
- nTotalMax = MAX_SAMPLES;
- nTempMax = pSndFile->GetNumSamples();
- }
- instr = ((instr * 10) + val) % 1000;
- if(instr >= nTotalMax)
- instr = instr % 100;
- if(nTempMax < 100) // if we're using samples & have less than 100 samples
- instr = instr % 100; // or if we're using instruments and have less than 100 instruments
- // --> ensure the entered instrument value is less than 100.
- target.instr = static_cast<ModCommand::INSTR>(instr);
- SetSelToCursor();
- if(target != oldcmd)
- {
- SetModified(false);
- InvalidateCell(m_Cursor);
- UpdateIndicator();
- }
- if(target.IsPcNote())
- {
- m_PCNoteEditMemory = target;
- }
- }
- // Enter a note in the pattern
- void CViewPattern::TempEnterNote(ModCommand::NOTE note, int vol, bool fromMidi)
- {
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- CModDoc *pModDoc = GetDocument();
- if(pMainFrm == nullptr || pModDoc == nullptr)
- {
- return;
- }
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- if(note < NOTE_MIN_SPECIAL)
- {
- Limit(note, sndFile.GetModSpecifications().noteMin, sndFile.GetModSpecifications().noteMax);
- }
- // Special case: Convert note off commands to C00 for MOD files
- if((sndFile.GetType() == MOD_TYPE_MOD) && (note == NOTE_NOTECUT || note == NOTE_FADE || note == NOTE_KEYOFF))
- {
- TempEnterFX(CMD_VOLUME, 0);
- return;
- }
- // Check whether the module format supports the note.
- if(sndFile.GetModSpecifications().HasNote(note) == false)
- {
- return;
- }
- const bool liveRecord = IsLiveRecord();
- const bool usePlaybackPosition = (liveRecord && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_AUTODELAY) && !sndFile.m_SongFlags[SONG_STEP]);
- const bool isSpecial = note >= NOTE_MIN_SPECIAL;
- const bool isSplit = IsNoteSplit(note);
- PatternEditPos editPos = GetEditPos(sndFile, usePlaybackPosition);
- const bool recordEnabled = IsEditingEnabled();
- CHANNELINDEX nChn = GetCurrentChannel();
- auto recordGroup = pModDoc->GetChannelRecordGroup(nChn);
- if(!isSpecial && pModDoc->GetSplitKeyboardSettings().IsSplitActive()
- && ((recordGroup == RecordGroup::Group1 && isSplit) || (recordGroup == RecordGroup::Group2 && !isSplit)))
- {
- // Record group 1 should be used for normal notes, record group 2 for split notes.
- // If there are any channels assigned to the "other" record group, we switch to another channel.
- auto otherGroup = (recordGroup == RecordGroup::Group1) ? RecordGroup::Group2 : RecordGroup::Group1;
- const CHANNELINDEX newChannel = FindGroupRecordChannel(otherGroup, true);
- if(newChannel != CHANNELINDEX_INVALID)
- {
- // Found a free channel, switch to other record group.
- nChn = newChannel;
- recordGroup = otherGroup;
- }
- }
- // -- Chord autodetection: step back if we just entered a note
- if(recordEnabled && recordGroup != RecordGroup::NoGroup && !liveRecord && !ModCommand::IsPcNote(note) && m_nSpacing > 0)
- {
- const auto &order = Order();
- if((timeGetTime() - m_autoChordStartTime) < TrackerSettings::Instance().gnAutoChordWaitTime
- && order.IsValidPat(m_autoChordStartOrder)
- && sndFile.Patterns[order[m_autoChordStartOrder]].IsValidRow(m_autoChordStartRow))
- {
- const auto pattern = order[m_autoChordStartOrder];
- if(pattern != editPos.pattern)
- {
- SetCurrentOrder(m_autoChordStartOrder);
- SetCurrentPattern(pattern, m_autoChordStartRow);
- }
- editPos.pattern = pattern;
- editPos.row = m_autoChordStartRow;
- } else
- {
- m_autoChordStartRow = ROWINDEX_INVALID;
- m_autoChordStartOrder = ORDERINDEX_INVALID;
- }
- m_autoChordStartTime = timeGetTime();
- if(m_autoChordStartOrder == ORDERINDEX_INVALID || m_autoChordStartRow == ROWINDEX_INVALID)
- {
- m_autoChordStartOrder = editPos.order;
- m_autoChordStartRow = editPos.row;
- }
- }
- // Quantize
- const bool doQuantize = (liveRecord || (fromMidi && (TrackerSettings::Instance().m_dwMidiSetup & MIDISETUP_PLAYPATTERNONMIDIIN))) && TrackerSettings::Instance().recordQuantizeRows != 0;
- if(doQuantize)
- {
- QuantizeRow(editPos.pattern, editPos.row);
- // "Grace notes" are stuffed into the next row, if possible
- if(sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn)->IsNote() && editPos.row < sndFile.Patterns[editPos.pattern].GetNumRows() - 1)
- {
- editPos.row++;
- }
- }
- // -- Work out where to put the new note
- ModCommand *pTarget = sndFile.Patterns[editPos.pattern].GetpModCommand(editPos.row, nChn);
- ModCommand newcmd = *pTarget;
- // Param control 'note'
- if(ModCommand::IsPcNote(note))
- {
- if(!pTarget->IsPcNote())
- {
- // We're overwriting a normal cell with a PC note.
- newcmd = m_PCNoteEditMemory;
- if((pTarget->command == CMD_MIDI || pTarget->command == CMD_SMOOTHMIDI) && pTarget->param < 128)
- {
- newcmd.SetValueEffectCol(static_cast<decltype(newcmd.GetValueEffectCol())>(Util::muldivr(pTarget->param, ModCommand::maxColumnValue, 127)));
- if(!newcmd.instr)
- newcmd.instr = sndFile.ChnSettings[nChn].nMixPlugin;
- auto activeMacro = sndFile.m_PlayState.Chn[nChn].nActiveMacro;
- if(!newcmd.GetValueVolCol() && sndFile.m_MidiCfg.GetParameteredMacroType(activeMacro) == kSFxPlugParam)
- {
- PlugParamIndex plugParam = sndFile.m_MidiCfg.MacroToPlugParam(sndFile.m_PlayState.Chn[nChn].nActiveMacro);
- if(plugParam < ModCommand::maxColumnValue)
- newcmd.SetValueVolCol(static_cast<decltype(newcmd.GetValueVolCol())>(plugParam));
- }
- }
- } else if(recordEnabled)
- {
- // Pick up current entry to update PC note edit memory.
- m_PCNoteEditMemory = newcmd;
- }
- newcmd.note = note;
- } else
- {
- // Are we overwriting a PC note here?
- if(pTarget->IsPcNote())
- {
- newcmd.Clear();
- }
- // -- write note and instrument data.
- HandleSplit(newcmd, note);
- // Nice idea actually: Use lower section of the keyboard to play chords (but it won't work 100% correctly this way...)
- /*if(isSplit)
- {
- TempEnterChord(note);
- return;
- }*/
- // -- write vol data
- int volWrite = -1;
- if(vol >= 0 && vol <= 64 && !(isSplit && pModDoc->GetSplitKeyboardSettings().splitVolume)) //write valid volume, as long as there's no split volume override.
- {
- volWrite = vol;
- } else if(isSplit && pModDoc->GetSplitKeyboardSettings().splitVolume) //cater for split volume override.
- {
- if(pModDoc->GetSplitKeyboardSettings().splitVolume > 0 && pModDoc->GetSplitKeyboardSettings().splitVolume <= 64)
- {
- volWrite = pModDoc->GetSplitKeyboardSettings().splitVolume;
- }
- }
- if(volWrite != -1 && !isSpecial)
- {
- if(sndFile.GetModSpecifications().HasVolCommand(VOLCMD_VOLUME))
- {
- newcmd.volcmd = VOLCMD_VOLUME;
- newcmd.vol = (ModCommand::VOL)volWrite;
- } else
- {
- newcmd.command = CMD_VOLUME;
- newcmd.param = (ModCommand::PARAM)volWrite;
- }
- }
- // -- write sdx if playing live
- if(usePlaybackPosition && m_nPlayTick && !doQuantize) // avoid SD0 which will be mis-interpreted
- {
- if(newcmd.command == CMD_NONE) //make sure we don't overwrite any existing commands.
- {
- newcmd.command = CMD_S3MCMDEX;
- if(!sndFile.GetModSpecifications().HasCommand(CMD_S3MCMDEX))
- newcmd.command = CMD_MODCMDEX;
- uint8 maxSpeed = 0x0F;
- if(m_nTicksOnRow > 0)
- maxSpeed = std::min(uint8(0x0F), mpt::saturate_cast<uint8>(m_nTicksOnRow - 1));
- newcmd.param = static_cast<ModCommand::PARAM>(0xD0 | std::min(maxSpeed, mpt::saturate_cast<uint8>(m_nPlayTick)));
- }
- }
- // Note cut/off/fade: erase instrument number
- if(newcmd.note >= NOTE_MIN_SPECIAL)
- newcmd.instr = 0;
- }
- // -- if recording, create undo point and write out modified command.
- const bool modified = (recordEnabled && *pTarget != newcmd);
- if(modified)
- {
- pModDoc->GetPatternUndo().PrepareUndo(editPos.pattern, nChn, editPos.row, 1, 1, "Note Entry");
- *pTarget = newcmd;
- }
- // -- play note
- if(((TrackerSettings::Instance().m_dwPatternSetup & (PATTERN_PLAYNEWNOTE | PATTERN_PLAYEDITROW)) || !recordEnabled) && !newcmd.IsPcNote())
- {
- const bool playWholeRow = ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !liveRecord);
- if(playWholeRow)
- {
- // play the whole row in "step mode"
- PatternStep(editPos.row);
- if(recordEnabled && newcmd.IsNote())
- m_noteChannel[newcmd.note - NOTE_MIN] = nChn;
- }
- if(!playWholeRow || !recordEnabled)
- {
- // NOTE: This code is *also* used for the PATTERN_PLAYEDITROW edit mode because of some unforseeable race conditions when modifying pattern data.
- // 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
- // data to the pattern and then remove it again - but often, it is actually removed before the row is parsed by the soundlib.
- // just play the newly inserted note using the already specified instrument...
- ModCommand::INSTR playIns = newcmd.instr;
- if(!playIns && ModCommand::IsNoteOrEmpty(note))
- {
- // ...or one that can be found on a previous row of this pattern.
- ModCommand *search = pTarget;
- ROWINDEX srow = editPos.row;
- while(srow-- > 0)
- {
- search -= sndFile.GetNumChannels();
- if(search->instr && !search->IsPcNote())
- {
- playIns = search->instr;
- m_fallbackInstrument = playIns; //used to figure out which instrument to stop on key release.
- break;
- }
- }
- }
- PlayNote(newcmd.note, playIns, 4 * vol, nChn);
- }
- }
- if(newcmd.IsNote())
- {
- m_previousNote[nChn] = note;
- }
- // -- if recording, handle post note entry behaviour (move cursor etc..)
- if(recordEnabled)
- {
- PatternCursor sel(editPos.row, nChn, m_Cursor.GetColumnType());
- if(!liveRecord)
- {
- // Update only when not recording live.
- SetCurSel(sel);
- }
- if(modified) // Has it really changed?
- {
- SetModified(false);
- if(editPos.pattern == m_nPattern)
- InvalidateCell(sel);
- else
- InvalidatePattern();
- if(!liveRecord)
- {
- // Update only when not recording live.
- UpdateIndicator();
- }
- }
- // Set new cursor position (edit step aka row spacing)
- if(!liveRecord)
- {
- if(m_nSpacing > 0)
- {
- if(editPos.row + m_nSpacing < sndFile.Patterns[editPos.pattern].GetNumRows() || (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL))
- {
- SetCurrentRow(editPos.row + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
- }
- }
- SetSelToCursor();
- }
- if(newcmd.IsPcNote())
- {
- // Nothing to do here anymore.
- return;
- }
- auto &activeNoteMap = isSplit ? m_splitActiveNoteChannel : m_activeNoteChannel;
- if(newcmd.note <= NOTE_MAX)
- activeNoteMap[newcmd.note] = static_cast<decltype(m_activeNoteChannel)::value_type>(nChn);
- if(recordGroup != RecordGroup::NoGroup)
- {
- // Move to next channel in record group
- nChn = FindGroupRecordChannel(recordGroup, false, nChn + 1);
- if(nChn != CHANNELINDEX_INVALID)
- {
- SetCurrentColumn(nChn);
- }
- }
- }
- }
- void CViewPattern::PlayNote(ModCommand::NOTE note, ModCommand::INSTR instr, int volume, CHANNELINDEX channel)
- {
- CModDoc *modDoc = GetDocument();
- modDoc->PlayNote(PlayNoteParam(note).Instrument(instr).Volume(volume).Channel(channel).CheckNNA(m_baPlayingNote), &m_noteChannel);
- }
- void CViewPattern::PreviewNote(ROWINDEX row, CHANNELINDEX channel)
- {
- const ModCommand &m = *GetSoundFile()->Patterns[m_nPattern].GetpModCommand(row, channel);
- if(m.IsNote() && m.instr)
- {
- int vol = -1;
- if(m.command == CMD_VOLUME)
- vol = m.param * 4u;
- else if(m.volcmd == VOLCMD_VOLUME)
- vol = m.vol * 4u;
- // Note-off any previews from this channel first
- ModCommand::NOTE note = NOTE_MIN;
- const auto &channels = GetSoundFile()->m_PlayState.Chn;
- for(auto &chn : m_noteChannel)
- {
- if(chn != CHANNELINDEX_INVALID && channels[chn].isPreviewNote && channels[chn].nMasterChn == channel + 1)
- {
- GetDocument()->NoteOff(note, false, m.instr, chn);
- }
- note++;
- }
- PlayNote(m.note, m.instr, vol, channel);
- }
- }
- // Construct a chord from the chord presets. Returns number of notes in chord.
- int CViewPattern::ConstructChord(int note, ModCommand::NOTE (&outNotes)[MPTChord::notesPerChord], ModCommand::NOTE baseNote)
- {
- const MPTChords &chords = TrackerSettings::GetChords();
- UINT chordNum = note - GetBaseNote();
- if(chordNum >= chords.size())
- {
- return 0;
- }
- const MPTChord &chord = chords[chordNum];
- const bool relativeMode = (chord.key == MPTChord::relativeMode); // Notes are relative to a previously entered note in the pattern
- ModCommand::NOTE key;
- if(relativeMode)
- {
- // Relative mode: Use pattern note as base note.
- // If there is no valid note in the pattern: Use shortcut note as relative base note
- key = ModCommand::IsNote(baseNote) ? baseNote : static_cast<ModCommand::NOTE>(note);
- } else
- {
- // Default mode: Use base key
- key = GetNoteWithBaseOctave(chord.key);
- }
- if(!ModCommand::IsNote(key))
- {
- return 0;
- }
- int numNotes = 0;
- const CModSpecifications &specs = GetSoundFile()->GetModSpecifications();
- if(specs.HasNote(key))
- {
- outNotes[numNotes++] = key;
- }
- int32 baseKey = key - NOTE_MIN;
- if(!relativeMode)
- {
- // Only use octave information from the base key
- baseKey = (baseKey / 12) * 12;
- }
- for(auto cnote : chord.notes)
- {
- if(cnote != MPTChord::noNote)
- {
- int32 chordNote = baseKey + cnote + NOTE_MIN;
- if(chordNote >= NOTE_MIN && chordNote <= NOTE_MAX && specs.HasNote(static_cast<ModCommand::NOTE>(chordNote)))
- {
- outNotes[numNotes++] = static_cast<ModCommand::NOTE>(chordNote);
- }
- }
- }
- return numNotes;
- }
- // Enter a chord in the pattern
- void CViewPattern::TempEnterChord(ModCommand::NOTE note)
- {
- CMainFrame *pMainFrm = CMainFrame::GetMainFrame();
- CModDoc *pModDoc = GetDocument();
- if(pMainFrm == nullptr || pModDoc == nullptr)
- {
- return;
- }
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- const CHANNELINDEX chn = GetCurrentChannel();
- const PatternRow rowBase = sndFile.Patterns[m_nPattern].GetRow(GetCurrentRow());
- ModCommand::NOTE chordNotes[MPTChord::notesPerChord], baseNote = rowBase[chn].note;
- if(!ModCommand::IsNote(baseNote))
- {
- baseNote = m_prevChordBaseNote;
- }
- int numNotes = ConstructChord(note, chordNotes, baseNote);
- if(!numNotes)
- {
- return;
- }
- // Save old row contents
- std::vector<ModCommand> newRow(rowBase, rowBase + sndFile.GetNumChannels());
- const bool liveRecord = IsLiveRecord();
- const bool recordEnabled = IsEditingEnabled();
- bool modified = false;
- // -- establish note data
- HandleSplit(newRow[chn], note);
- const auto recordGroup = pModDoc->GetChannelRecordGroup(chn);
- CHANNELINDEX curChn = chn;
- for(int i = 0; i < numNotes; i++)
- {
- // Find appropriate channel
- while(curChn < sndFile.GetNumChannels() && pModDoc->GetChannelRecordGroup(curChn) != recordGroup)
- {
- curChn++;
- }
- if(curChn >= sndFile.GetNumChannels())
- {
- numNotes = i;
- break;
- }
- m_chordPatternChannels[i] = curChn;
- ModCommand &m = newRow[curChn];
- m_previousNote[curChn] = m.note = chordNotes[i];
- if(newRow[chn].instr)
- {
- m.instr = newRow[chn].instr;
- }
- if(rowBase[chn] != m)
- {
- modified = true;
- }
- curChn++;
- }
- m_Status.set(psChordPlaying);
- // -- write notedata
- if(recordEnabled)
- {
- SetSelToCursor();
- if(modified)
- {
- // Simply backup the whole row.
- pModDoc->GetPatternUndo().PrepareUndo(m_nPattern, chn, GetCurrentRow(), sndFile.GetNumChannels(), 1, "Chord Entry");
- for(CHANNELINDEX n = 0; n < sndFile.GetNumChannels(); n++)
- {
- rowBase[n] = newRow[n];
- }
- SetModified(false);
- InvalidateRow();
- UpdateIndicator();
- }
- }
- // -- play note
- if((TrackerSettings::Instance().m_dwPatternSetup & (PATTERN_PLAYNEWNOTE | PATTERN_PLAYEDITROW)) || !recordEnabled)
- {
- if(m_prevChordNote != NOTE_NONE)
- {
- TempStopChord(m_prevChordNote);
- }
- const bool playWholeRow = ((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !liveRecord);
- if(playWholeRow)
- {
- // play the whole row in "step mode"
- PatternStep(GetCurrentRow());
- if(recordEnabled)
- {
- for(int i = 0; i < numNotes; i++)
- {
- m_noteChannel[chordNotes[i] - NOTE_MIN] = m_chordPatternChannels[i];
- }
- }
- }
- if(!playWholeRow || !recordEnabled)
- {
- // NOTE: This code is *also* used for the PATTERN_PLAYEDITROW edit mode because of some unforseeable race conditions when modifying pattern data.
- // 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
- // data to the pattern and then remove it again - but often, it is actually removed before the row is parsed by the soundlib.
- // just play the newly inserted notes...
- const ModCommand &firstNote = rowBase[chn];
- ModCommand::INSTR playIns = 0;
- if(firstNote.instr)
- {
- // ...using the already specified instrument
- playIns = firstNote.instr;
- } else if(!firstNote.instr)
- {
- // ...or one that can be found on a previous row of this pattern.
- const ModCommand *search = &firstNote;
- ROWINDEX srow = GetCurrentRow();
- while(srow-- > 0)
- {
- search -= sndFile.GetNumChannels();
- if(search->instr)
- {
- playIns = search->instr;
- m_fallbackInstrument = playIns; //used to figure out which instrument to stop on key release.
- break;
- }
- }
- }
- for(int i = 0; i < numNotes; i++)
- {
- pModDoc->PlayNote(PlayNoteParam(chordNotes[i]).Instrument(playIns).Channel(chn).CheckNNA(m_baPlayingNote), &m_noteChannel);
- }
- }
- } // end play note
- m_prevChordNote = note;
- m_prevChordBaseNote = baseNote;
- // Set new cursor position (edit step aka row spacing) - only when not recording live
- if(recordEnabled && !liveRecord)
- {
- if(m_nSpacing > 0)
- {
- // Shift from entering chord may have triggered this flag, which will prevent us from wrapping to the next pattern.
- m_Status.reset(psKeyboardDragSelect);
- SetCurrentRow(GetCurrentRow() + m_nSpacing, (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_CONTSCROLL) != 0);
- }
- SetSelToCursor();
- }
- }
- // Translate incoming MIDI aftertouch messages to pattern commands
- void CViewPattern::EnterAftertouch(ModCommand::NOTE note, int atValue)
- {
- if(TrackerSettings::Instance().aftertouchBehaviour == atDoNotRecord || !IsEditingEnabled())
- return;
- const CHANNELINDEX numChannels = GetSoundFile()->GetNumChannels();
- std::set<CHANNELINDEX> channels;
- if(ModCommand::IsNote(note))
- {
- // For polyphonic aftertouch, map the aftertouch note to the correct pattern channel.
- const auto &activeNoteMap = IsNoteSplit(note) ? m_splitActiveNoteChannel : m_activeNoteChannel;
- if(activeNoteMap[note] < numChannels)
- {
- channels.insert(activeNoteMap[note]);
- } else
- {
- // Couldn't find the channel that belongs to this note... Don't bother writing aftertouch messages.
- // This is actually necessary, because it is possible that the last aftertouch message for a note
- // is received after the key-off event, in which case OpenMPT won't know anymore on which channel
- // that particular note was, so it will just put the message on some other channel. We don't want that!
- return;
- }
- } else
- {
- for(const auto ¬eMap : { m_activeNoteChannel, m_splitActiveNoteChannel })
- {
- for(const auto chn : noteMap)
- {
- if(chn < numChannels)
- channels.insert(chn);
- }
- }
- if(channels.empty())
- channels.insert(m_Cursor.GetChannel());
- }
- Limit(atValue, 0, 127);
- const PatternCursor endOfRow{ m_Cursor.GetRow(), static_cast<CHANNELINDEX>(numChannels - 1u), PatternCursor::lastColumn };
- const auto &specs = GetSoundFile()->GetModSpecifications();
- bool first = true, modified = false;
- for(const auto chn : channels)
- {
- const PatternCursor cursor{ m_Cursor.GetRow(), chn };
- ModCommand &target = GetModCommand(cursor);
- ModCommand newCommand = target;
- if(target.IsPcNote())
- continue;
- switch(TrackerSettings::Instance().aftertouchBehaviour)
- {
- case atRecordAsVolume:
- // Record aftertouch messages as volume commands
- if(specs.HasVolCommand(VOLCMD_VOLUME))
- {
- if(newCommand.volcmd == VOLCMD_NONE || newCommand.volcmd == VOLCMD_VOLUME)
- {
- newCommand.volcmd = VOLCMD_VOLUME;
- newCommand.vol = static_cast<ModCommand::VOL>((atValue * 64 + 64) / 127);
- }
- } else if(specs.HasCommand(CMD_VOLUME))
- {
- if(newCommand.command == CMD_NONE || newCommand.command == CMD_VOLUME)
- {
- newCommand.command = CMD_VOLUME;
- newCommand.param = static_cast<ModCommand::PARAM>((atValue * 64 + 64) / 127);
- }
- }
- break;
- case atRecordAsMacro:
- // Record aftertouch messages as MIDI Macros
- if(newCommand.command == CMD_NONE || newCommand.command == CMD_SMOOTHMIDI || newCommand.command == CMD_MIDI)
- {
- auto cmd =
- specs.HasCommand(CMD_SMOOTHMIDI) ? CMD_SMOOTHMIDI :
- specs.HasCommand(CMD_MIDI) ? CMD_MIDI :
- CMD_NONE;
- if(cmd != CMD_NONE)
- {
- newCommand.command = static_cast<ModCommand::COMMAND>(cmd);
- newCommand.param = static_cast<ModCommand::PARAM>(atValue);
- }
- }
- break;
- }
- if(target != newCommand)
- {
- if(first)
- PrepareUndo(cursor, endOfRow, "Aftertouch Entry");
- first = false;
- modified = true;
- target = newCommand;
- InvalidateCell(cursor);
- }
- }
- if(modified)
- {
- SetModified(false);
- UpdateIndicator();
- }
- }
- // Apply quantization factor to given row.
- void CViewPattern::QuantizeRow(PATTERNINDEX &pat, ROWINDEX &row) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || TrackerSettings::Instance().recordQuantizeRows == 0)
- {
- return;
- }
- const ROWINDEX currentTick = m_nTicksOnRow * row + m_nPlayTick;
- const ROWINDEX ticksPerNote = TrackerSettings::Instance().recordQuantizeRows * m_nTicksOnRow;
- // Previous quantization step
- const ROWINDEX quantLow = (currentTick / ticksPerNote) * ticksPerNote;
- // Next quantization step
- const ROWINDEX quantHigh = (1 + (currentTick / ticksPerNote)) * ticksPerNote;
- if(currentTick - quantLow < quantHigh - currentTick)
- {
- row = quantLow / m_nTicksOnRow;
- } else
- {
- row = quantHigh / m_nTicksOnRow;
- }
- if(!sndFile->Patterns[pat].IsValidRow(row))
- {
- // Quantization exceeds current pattern, try stuffing note into next pattern instead.
- PATTERNINDEX nextPat = sndFile->m_SongFlags[SONG_PATTERNLOOP] ? m_nPattern : GetNextPattern();
- if(nextPat != PATTERNINDEX_INVALID)
- {
- pat = nextPat;
- row = 0;
- } else
- {
- row = sndFile->Patterns[pat].GetNumRows() - 1;
- }
- }
- }
- // Get previous pattern in order list
- PATTERNINDEX CViewPattern::GetPrevPattern() const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile != nullptr)
- {
- const auto &order = Order();
- const ORDERINDEX curOrder = GetCurrentOrder();
- if(curOrder > 0 && m_nPattern == order[curOrder])
- {
- const ORDERINDEX nextOrder = order.GetPreviousOrderIgnoringSkips(curOrder);
- const PATTERNINDEX nextPat = order[nextOrder];
- if(sndFile->Patterns.IsValidPat(nextPat) && sndFile->Patterns[nextPat].GetNumRows())
- {
- return nextPat;
- }
- }
- }
- return PATTERNINDEX_INVALID;
- }
- // Get follow-up pattern in order list
- PATTERNINDEX CViewPattern::GetNextPattern() const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile != nullptr)
- {
- const auto &order = Order();
- const ORDERINDEX curOrder = GetCurrentOrder();
- if(curOrder + 1 < order.GetLength() && m_nPattern == order[curOrder])
- {
- const ORDERINDEX nextOrder = order.GetNextOrderIgnoringSkips(curOrder);
- const PATTERNINDEX nextPat = order[nextOrder];
- if(sndFile->Patterns.IsValidPat(nextPat) && sndFile->Patterns[nextPat].GetNumRows())
- {
- return nextPat;
- }
- }
- }
- return PATTERNINDEX_INVALID;
- }
- void CViewPattern::OnSetQuantize()
- {
- CInputDlg dlg(this, _T("Quantize amount in rows for live recording (0 to disable):"), 0, MAX_PATTERN_ROWS, TrackerSettings::Instance().recordQuantizeRows);
- if(dlg.DoModal())
- {
- TrackerSettings::Instance().recordQuantizeRows = static_cast<ROWINDEX>(dlg.resultAsInt);
- }
- }
- void CViewPattern::OnLockPatternRows()
- {
- CSoundFile &sndFile = *GetSoundFile();
- if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
- {
- sndFile.m_lockRowStart = m_Selection.GetStartRow();
- sndFile.m_lockRowEnd = m_Selection.GetEndRow();
- } else
- {
- sndFile.m_lockRowStart = sndFile.m_lockRowEnd = ROWINDEX_INVALID;
- }
- InvalidatePattern(true, true);
- }
- // Find a free channel for a record group, starting search from a given channel.
- // If forceFreeChannel is true and all channels in the specified record group are active, some channel is picked from the specified record group.
- CHANNELINDEX CViewPattern::FindGroupRecordChannel(RecordGroup recordGroup, bool forceFreeChannel, CHANNELINDEX startChannel) const
- {
- const CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return CHANNELINDEX_INVALID;
- CHANNELINDEX chn = startChannel;
- CHANNELINDEX foundChannel = CHANNELINDEX_INVALID;
- for(CHANNELINDEX i = 1; i < pModDoc->GetNumChannels(); i++, chn++)
- {
- if(chn >= pModDoc->GetNumChannels())
- chn = 0; // loop around
- if(pModDoc->GetChannelRecordGroup(chn) == recordGroup)
- {
- // Check if any notes are playing on this channel
- bool channelLocked = false;
- for(size_t k = 0; k < m_activeNoteChannel.size(); k++)
- {
- if(m_activeNoteChannel[k] == chn || m_splitActiveNoteChannel[k] == chn)
- {
- channelLocked = true;
- break;
- }
- }
- if(!channelLocked)
- {
- // Channel belongs to correct record group and no note is currently playing.
- return chn;
- }
- if(forceFreeChannel)
- {
- // If all channels are active, we might still pick a random channel from the specified group.
- foundChannel = chn;
- }
- }
- }
- return foundChannel;
- }
- void CViewPattern::OnClearField(const RowMask &mask, bool step, bool ITStyle)
- {
- CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !IsEditingEnabled_bmsg())
- return;
- // If we have a selection, we want to do something different
- if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
- {
- OnClearSelection(ITStyle);
- return;
- }
- PrepareUndo(m_Cursor, m_Cursor, "Clear Field");
- ModCommand &target = GetCursorCommand();
- ModCommand oldcmd = target;
- if(mask.note)
- {
- // Clear note
- if(target.IsPcNote())
- {
- // Need to clear entire field if this is a PC Event.
- target.Clear();
- } else
- {
- target.note = NOTE_NONE;
- if(ITStyle)
- {
- target.instr = 0;
- }
- }
- }
- if(mask.instrument)
- {
- // Clear instrument
- target.instr = 0;
- }
- if(mask.volume)
- {
- // Clear volume effect
- target.volcmd = VOLCMD_NONE;
- target.vol = 0;
- }
- if(mask.command)
- {
- // Clear effect command
- target.command = CMD_NONE;
- }
- if(mask.parameter)
- {
- // Clear effect parameter
- target.param = 0;
- }
- if((mask.command || mask.parameter) && (target.IsPcNote()))
- {
- target.SetValueEffectCol(0);
- }
- SetSelToCursor();
- if(target != oldcmd)
- {
- SetModified(false);
- InvalidateRow();
- UpdateIndicator();
- }
- if(step && (sndFile->IsPaused() || !m_Status[psFollowSong] ||
- (CMainFrame::GetMainFrame() != nullptr && CMainFrame::GetMainFrame()->GetFollowSong(GetDocument()) != m_hWnd)))
- {
- // Preview Row
- if((TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYEDITROW) && !IsLiveRecord())
- {
- PatternStep(GetCurrentRow());
- }
- if(m_nSpacing > 0)
- SetCurrentRow(GetCurrentRow() + m_nSpacing);
- SetSelToCursor();
- }
- }
- void CViewPattern::OnInitMenu(CMenu *pMenu)
- {
- CModScrollView::OnInitMenu(pMenu);
- }
- void CViewPattern::TogglePluginEditor(int chan)
- {
- CModDoc *modDoc = GetDocument();
- if(!modDoc)
- return;
- int plug = modDoc->GetSoundFile().ChnSettings[chan].nMixPlugin;
- if(plug > 0)
- modDoc->TogglePluginEditor(plug - 1);
- return;
- }
- void CViewPattern::OnSelectInstrument(UINT nID)
- {
- SetSelectionInstrument(static_cast<INSTRUMENTINDEX>(nID - ID_CHANGE_INSTRUMENT), true);
- }
- void CViewPattern::OnSelectPCNoteParam(UINT nID)
- {
- CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
- return;
- uint16 paramNdx = static_cast<uint16>(nID - ID_CHANGE_PCNOTE_PARAM);
- bool modified = false;
- ApplyToSelection([paramNdx, &modified] (ModCommand &m, ROWINDEX, CHANNELINDEX)
- {
- if(m.IsPcNote() && (m.GetValueVolCol() != paramNdx))
- {
- m.SetValueVolCol(paramNdx);
- modified = true;
- }
- });
- if(modified)
- {
- SetModified();
- InvalidatePattern();
- }
- }
- void CViewPattern::OnSelectPlugin(UINT nID)
- {
- CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr)
- return;
- const CHANNELINDEX plugChannel = m_MenuCursor.GetChannel();
- if(plugChannel < sndFile->GetNumChannels())
- {
- PLUGINDEX newPlug = static_cast<PLUGINDEX>(nID - ID_PLUGSELECT);
- if(newPlug <= MAX_MIXPLUGINS && newPlug != sndFile->ChnSettings[plugChannel].nMixPlugin)
- {
- sndFile->ChnSettings[plugChannel].nMixPlugin = newPlug;
- if(sndFile->GetModSpecifications().supportsPlugins)
- {
- SetModified(false);
- }
- InvalidateChannelsHeaders();
- }
- }
- }
- bool CViewPattern::HandleSplit(ModCommand &m, int note)
- {
- ModCommand::INSTR ins = static_cast<ModCommand::INSTR>(GetCurrentInstrument());
- const bool isSplit = IsNoteSplit(note);
- if(isSplit)
- {
- CModDoc *modDoc = GetDocument();
- if(modDoc == nullptr)
- return false;
- const CSoundFile &sndFile = modDoc->GetSoundFile();
- if(modDoc->GetSplitKeyboardSettings().octaveLink && note <= NOTE_MAX)
- {
- note += 12 * modDoc->GetSplitKeyboardSettings().octaveModifier;
- Limit(note, sndFile.GetModSpecifications().noteMin, sndFile.GetModSpecifications().noteMax);
- }
- if(modDoc->GetSplitKeyboardSettings().splitInstrument)
- {
- ins = modDoc->GetSplitKeyboardSettings().splitInstrument;
- }
- }
- m.note = static_cast<ModCommand::NOTE>(note);
- if(ins)
- {
- m.instr = ins;
- }
- return isSplit;
- }
- bool CViewPattern::IsNoteSplit(int note) const
- {
- CModDoc *pModDoc = GetDocument();
- return (pModDoc != nullptr
- && pModDoc->GetSplitKeyboardSettings().IsSplitActive()
- && note <= pModDoc->GetSplitKeyboardSettings().splitNote);
- }
- bool CViewPattern::BuildPluginCtxMenu(HMENU hMenu, UINT nChn, const CSoundFile &sndFile) const
- {
- for(PLUGINDEX plug = 0; plug <= MAX_MIXPLUGINS; plug++)
- {
- bool itemFound = false;
- CString s;
- if(!plug)
- {
- s = _T("No Plugin");
- itemFound = true;
- } else
- {
- const SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[plug - 1];
- if(plugin.IsValidPlugin())
- {
- s.Format(_T("FX%u: "), plug);
- s += mpt::ToCString(plugin.GetName());
- itemFound = true;
- }
- }
- if(itemFound)
- {
- UINT flags = MF_STRING | ((plug == sndFile.ChnSettings[nChn].nMixPlugin) ? MF_CHECKED : 0);
- AppendMenu(hMenu, flags, ID_PLUGSELECT + plug, s);
- }
- }
- return true;
- }
- bool CViewPattern::BuildSoloMuteCtxMenu(HMENU hMenu, CInputHandler *ih, UINT nChn, const CSoundFile &sndFile) const
- {
- AppendMenu(hMenu, sndFile.ChnSettings[nChn].dwFlags[CHN_MUTE] ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_PATTERN_MUTE, ih->GetKeyTextFromCommand(kcChannelMute, _T("&Mute Channel")));
- bool solo = false, unmuteAll = false;
- bool soloPending = false, unmuteAllPending = false; // doesn't work perfectly yet
- for(CHANNELINDEX i = 0; i < sndFile.GetNumChannels(); i++)
- {
- if(i != nChn)
- {
- if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
- solo = soloPending = true;
- if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
- soloPending = true;
- } else
- {
- if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
- solo = soloPending = true;
- if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
- soloPending = true;
- }
- if(sndFile.ChnSettings[i].dwFlags[CHN_MUTE])
- unmuteAll = unmuteAllPending = true;
- if(!sndFile.ChnSettings[i].dwFlags[CHN_MUTE] && sndFile.m_bChannelMuteTogglePending[i])
- unmuteAllPending = true;
- }
- if(solo)
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_SOLO, ih->GetKeyTextFromCommand(kcChannelSolo, _T("&Solo Channel")));
- if(unmuteAll)
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_UNMUTEALL, ih->GetKeyTextFromCommand(kcChannelUnmuteAll, _T("&Unmute All")));
- 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")));
- if(unmuteAllPending)
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSITION_UNMUTEALL, ih->GetKeyTextFromCommand(kcUnmuteAllChnOnPatTransition, _T("On Transition: Unmute All")));
- if(soloPending)
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSITIONSOLO, ih->GetKeyTextFromCommand(kcSoloChnOnPatTransition, _T("On Transition: Solo")));
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_CHNRESET, ih->GetKeyTextFromCommand(kcChannelReset, _T("&Reset Channel")));
- return true;
- }
- bool CViewPattern::BuildRecordCtxMenu(HMENU hMenu, CInputHandler *ih, CHANNELINDEX nChn) const
- {
- const auto recordGroup = GetDocument()->GetChannelRecordGroup(nChn);
- AppendMenu(hMenu, (recordGroup == RecordGroup::Group1) ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_EDIT_RECSELECT, ih->GetKeyTextFromCommand(kcChannelRecordSelect, _T("R&ecord Select")));
- AppendMenu(hMenu, (recordGroup == RecordGroup::Group2) ? (MF_STRING | MF_CHECKED) : MF_STRING, ID_EDIT_SPLITRECSELECT, ih->GetKeyTextFromCommand(kcChannelSplitRecordSelect, _T("S&plit Record Select")));
- return true;
- }
- bool CViewPattern::BuildRowInsDelCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- HMENU subMenuInsert = CreatePopupMenu();
- HMENU subMenuDelete = CreatePopupMenu();
- const auto numRows = m_Selection.GetNumRows();
- const CString label = (numRows != 1) ? MPT_CFORMAT("{} Rows")(numRows) : CString(_T("Row"));
- AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTROW, ih->GetKeyTextFromCommand(kcInsertRow, _T("Insert ") + label + _T(" (&Selection)")));
- AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTALLROW, ih->GetKeyTextFromCommand(kcInsertWholeRow, _T("Insert ") + label + _T(" (&All Channels)")));
- AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTROWGLOBAL, ih->GetKeyTextFromCommand(kcInsertRowGlobal, _T("Insert ") + label + _T(" (Selection, &Global)")));
- AppendMenu(subMenuInsert, MF_STRING, ID_PATTERN_INSERTALLROWGLOBAL, ih->GetKeyTextFromCommand(kcInsertWholeRowGlobal, _T("Insert ") + label + _T(" (All &Channels, Global)")));
- AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(subMenuInsert), _T("&Insert ") + label);
- AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEROW, ih->GetKeyTextFromCommand(kcDeleteRow, _T("Delete ") + label + _T(" (&Selection)")));
- AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEALLROW, ih->GetKeyTextFromCommand(kcDeleteWholeRow, _T("Delete ") + label + _T(" (&All Channels)")));
- AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEROWGLOBAL, ih->GetKeyTextFromCommand(kcDeleteRowGlobal, _T("Delete ") + label + _T(" (Selection, &Global)")));
- AppendMenu(subMenuDelete, MF_STRING, ID_PATTERN_DELETEALLROWGLOBAL, ih->GetKeyTextFromCommand(kcDeleteWholeRowGlobal, _T("Delete ") + label + _T(" (All &Channels, Global)")));
- AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(subMenuDelete), _T("&Delete ") + label);
- return true;
- }
- bool CViewPattern::BuildMiscCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- AppendMenu(hMenu, MF_STRING, ID_SHOWTIMEATROW, ih->GetKeyTextFromCommand(kcTimeAtRow, _T("Show Row Play Time")));
- if(m_Selection.GetStartRow() == m_Selection.GetEndRow())
- {
- CString s;
- s.Format(_T("Split Pattern at Ro&w %u"), m_Selection.GetStartRow());
- AppendMenu(hMenu, MF_STRING | (m_Selection.GetStartRow() < 1 ? MF_GRAYED : 0), ID_PATTERN_SPLIT, ih->GetKeyTextFromCommand(kcSplitPattern, s));
- }
- const CSoundFile &sndFile = *GetSoundFile();
- CString lockStr;
- bool lockActive = (sndFile.m_lockRowStart != ROWINDEX_INVALID);
- if(m_Selection.GetUpperLeft() != m_Selection.GetLowerRight())
- {
- lockStr = _T("&Lock Playback to Selection");
- if(lockActive)
- {
- lockStr.AppendFormat(_T(" (Current: %u-%u)"), sndFile.m_lockRowStart, sndFile.m_lockRowEnd);
- }
- } else if(lockActive)
- {
- lockStr = _T("Reset Playback &Lock");
- } else
- {
- return true;
- }
- AppendMenu(hMenu, MF_STRING | (lockActive ? MF_CHECKED : 0), ID_LOCK_PATTERN_ROWS, ih->GetKeyTextFromCommand(kcLockPlaybackToRows, lockStr));
- return true;
- }
- bool CViewPattern::BuildSelectionCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- AppendMenu(hMenu, MF_STRING, ID_EDIT_SELECTCOLUMN, ih->GetKeyTextFromCommand(kcSelectChannel, _T("Select &Channel")));
- AppendMenu(hMenu, MF_STRING, ID_EDIT_SELECT_ALL, ih->GetKeyTextFromCommand(kcEditSelectAll, _T("Select &Pattern")));
- return true;
- }
- bool CViewPattern::BuildGrowShrinkCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- AppendMenu(hMenu, MF_STRING, ID_GROW_SELECTION, ih->GetKeyTextFromCommand(kcPatternGrowSelection, _T("&Grow selection")));
- AppendMenu(hMenu, MF_STRING, ID_SHRINK_SELECTION, ih->GetKeyTextFromCommand(kcPatternShrinkSelection, _T("&Shrink selection")));
- return true;
- }
- bool CViewPattern::BuildInterpolationCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- const bool isPCNote = sndFile->Patterns.IsValidPat(m_nPattern) && sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel())->IsPcNote();
- HMENU subMenu = CreatePopupMenu();
- bool possible = BuildInterpolationCtxMenu(subMenu, PatternCursor::noteColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateNote, _T("&Note Column")), ID_PATTERN_INTERPOLATE_NOTE)
- | BuildInterpolationCtxMenu(subMenu, PatternCursor::instrColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateInstr, isPCNote ? _T("&Plugin Column") : _T("&Instrument Column")), ID_PATTERN_INTERPOLATE_INSTR)
- | BuildInterpolationCtxMenu(subMenu, PatternCursor::volumeColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateVol, isPCNote ? _T("&Parameter Column") : _T("&Volume Column")), ID_PATTERN_INTERPOLATE_VOLUME)
- | BuildInterpolationCtxMenu(subMenu, PatternCursor::effectColumn, ih->GetKeyTextFromCommand(kcPatternInterpolateEffect, isPCNote ? _T("&Value Column") : _T("&Effect Column")), ID_PATTERN_INTERPOLATE_EFFECT);
- if(possible || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_POPUP | (possible ? 0 : MF_GRAYED), reinterpret_cast<UINT_PTR>(subMenu), _T("I&nterpolate..."));
- return true;
- }
- return false;
- }
- bool CViewPattern::BuildInterpolationCtxMenu(HMENU hMenu, PatternCursor::Columns colType, CString label, UINT command) const
- {
- bool possible = IsInterpolationPossible(colType);
- if(!possible && colType == PatternCursor::effectColumn)
- {
- // Extend search to param column
- possible = IsInterpolationPossible(PatternCursor::paramColumn);
- }
- if(possible || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_STRING | (possible ? 0 : MF_GRAYED), command, label);
- }
- return possible;
- }
- bool CViewPattern::BuildEditCtxMenu(HMENU hMenu, CInputHandler *ih, CModDoc *pModDoc) const
- {
- HMENU pasteSpecialMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_STRING, ID_EDIT_CUT, ih->GetKeyTextFromCommand(kcEditCut, _T("Cu&t")));
- AppendMenu(hMenu, MF_STRING, ID_EDIT_COPY, ih->GetKeyTextFromCommand(kcEditCopy, _T("&Copy")));
- AppendMenu(hMenu, MF_STRING | (PatternClipboard::CanPaste() ? 0 : MF_GRAYED), ID_EDIT_PASTE, ih->GetKeyTextFromCommand(kcEditPaste, _T("&Paste")));
- AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(pasteSpecialMenu), _T("Paste Special"));
- AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_MIXPASTE, ih->GetKeyTextFromCommand(kcEditMixPaste, _T("&Mix Paste")));
- AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_MIXPASTE_ITSTYLE, ih->GetKeyTextFromCommand(kcEditMixPasteITStyle, _T("M&ix Paste (IT Style)")));
- AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_PASTEFLOOD, ih->GetKeyTextFromCommand(kcEditPasteFlood, _T("Paste Fl&ood")));
- AppendMenu(pasteSpecialMenu, MF_STRING, ID_EDIT_PUSHFORWARDPASTE, ih->GetKeyTextFromCommand(kcEditPushForwardPaste, _T("&Push Forward Paste (Insert)")));
- DWORD greyed = pModDoc->GetPatternUndo().CanUndo() ? MF_ENABLED : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_STRING | greyed, ID_EDIT_UNDO, ih->GetKeyTextFromCommand(kcEditUndo, _T("&Undo")));
- }
- greyed = pModDoc->GetPatternUndo().CanRedo() ? MF_ENABLED : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_STRING | greyed, ID_EDIT_REDO, ih->GetKeyTextFromCommand(kcEditRedo, _T("&Redo")));
- }
- AppendMenu(hMenu, MF_STRING, ID_CLEAR_SELECTION, ih->GetKeyTextFromCommand(kcSampleDelete, _T("Clear Selection")));
- return true;
- }
- bool CViewPattern::BuildVisFXCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- DWORD greyed = (IsColumnSelected(PatternCursor::effectColumn) || IsColumnSelected(PatternCursor::paramColumn)) ? FALSE : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_VISUALIZE_EFFECT, ih->GetKeyTextFromCommand(kcPatternVisualizeEffect, _T("&Visualize Effect")));
- return true;
- }
- return false;
- }
- bool CViewPattern::BuildTransposeCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- HMENU transMenu = CreatePopupMenu();
- std::vector<CHANNELINDEX> validChans;
- DWORD greyed = IsColumnSelected(PatternCursor::noteColumn) ? FALSE : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_UP, ih->GetKeyTextFromCommand(kcTransposeUp, _T("Transpose +&1")));
- AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_DOWN, ih->GetKeyTextFromCommand(kcTransposeDown, _T("Transpose -&1")));
- AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_OCTUP, ih->GetKeyTextFromCommand(kcTransposeOctUp, _T("Transpose +1&2")));
- AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_OCTDOWN, ih->GetKeyTextFromCommand(kcTransposeOctDown, _T("Transpose -1&2")));
- AppendMenu(transMenu, MF_STRING | greyed, ID_TRANSPOSE_CUSTOM, ih->GetKeyTextFromCommand(kcTransposeCustom, _T("&Custom...")));
- AppendMenu(hMenu, MF_POPUP | greyed, reinterpret_cast<UINT_PTR>(transMenu), _T("&Transpose..."));
- return true;
- }
- return false;
- }
- bool CViewPattern::BuildAmplifyCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- std::vector<CHANNELINDEX> validChans;
- DWORD greyed = IsColumnSelected(PatternCursor::volumeColumn) ? 0 : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- AppendMenu(hMenu, MF_STRING | greyed, ID_PATTERN_AMPLIFY, ih->GetKeyTextFromCommand(kcPatternAmplify, _T("&Amplify")));
- return true;
- }
- return false;
- }
- bool CViewPattern::BuildChannelControlCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- const CModSpecifications &specs = GetDocument()->GetSoundFile().GetModSpecifications();
- CHANNELINDEX numChannels = GetDocument()->GetNumChannels();
- DWORD canAddChannels = (numChannels < specs.channelsMax) ? 0 : MF_GRAYED;
- DWORD canRemoveChannels = (numChannels > specs.channelsMin) ? 0 : MF_GRAYED;
- AppendMenu(hMenu, MF_SEPARATOR, 0, _T(""));
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_TRANSPOSECHANNEL, ih->GetKeyTextFromCommand(kcChannelTranspose, _T("&Transpose Channel")));
- AppendMenu(hMenu, MF_STRING | canAddChannels, ID_PATTERN_DUPLICATECHANNEL, ih->GetKeyTextFromCommand(kcChannelDuplicate, _T("&Duplicate Channel")));
- HMENU addChannelMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_POPUP | canAddChannels, reinterpret_cast<UINT_PTR>(addChannelMenu), _T("&Add Channel\t"));
- AppendMenu(addChannelMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_FRONT, ih->GetKeyTextFromCommand(kcChannelAddBefore, _T("&Before this channel")));
- AppendMenu(addChannelMenu, MF_STRING, ID_PATTERN_ADDCHANNEL_AFTER, ih->GetKeyTextFromCommand(kcChannelAddAfter, _T("&After this channel")));
- HMENU removeChannelMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_POPUP | canRemoveChannels, reinterpret_cast<UINT_PTR>(removeChannelMenu), _T("Remo&ve Channel\t"));
- AppendMenu(removeChannelMenu, MF_STRING, ID_PATTERN_REMOVECHANNEL, ih->GetKeyTextFromCommand(kcChannelRemove, _T("&Remove this channel\t")));
- AppendMenu(removeChannelMenu, MF_STRING, ID_PATTERN_REMOVECHANNELDIALOG, _T("&Choose channels to remove...\t"));
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_RESETCHANNELCOLORS, _T("Reset Channel &Colours"));
- return false;
- }
- bool CViewPattern::BuildSetInstCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- const CModDoc *modDoc;
- if(sndFile == nullptr || (modDoc = sndFile->GetpModDoc()) == nullptr)
- {
- return false;
- }
- std::vector<CHANNELINDEX> validChans;
- DWORD greyed = IsColumnSelected(PatternCursor::instrColumn) ? 0 : MF_GRAYED;
- if(!greyed || !(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_OLDCTXMENUSTYLE))
- {
- if((sndFile->Patterns.IsValidPat(m_nPattern)))
- {
- if(sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel())->IsPcNote())
- {
- // Don't build instrument menu for PC notes.
- return false;
- }
- }
- // Create the new menu and add it to the existing menu.
- HMENU instrumentChangeMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_POPUP | greyed, reinterpret_cast<UINT_PTR>(instrumentChangeMenu), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("Change Instrument")));
- if(!greyed)
- {
- bool addSeparator = false;
- if(sndFile->GetNumInstruments())
- {
- for(INSTRUMENTINDEX i = 1; i <= sndFile->GetNumInstruments(); i++)
- {
- if(sndFile->Instruments[i] == nullptr)
- continue;
- CString instString = modDoc->GetPatternViewInstrumentName(i, true);
- if(!instString.IsEmpty())
- {
- AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + i, modDoc->GetPatternViewInstrumentName(i));
- addSeparator = true;
- }
- }
- } else
- {
- CString s;
- for(SAMPLEINDEX i = 1; i <= sndFile->GetNumSamples(); i++) if (sndFile->GetSample(i).HasSampleData())
- {
- s.Format(_T("%02d: "), i);
- s += mpt::ToCString(sndFile->GetCharsetInternal(), sndFile->GetSampleName(i));
- AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + i, s);
- addSeparator = true;
- }
- }
- // Add options to remove instrument from selection.
- if(addSeparator)
- {
- AppendMenu(instrumentChangeMenu, MF_SEPARATOR, 0, 0);
- }
- AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT, _T("&Remove Instrument"));
- AppendMenu(instrumentChangeMenu, MF_STRING, ID_CHANGE_INSTRUMENT + GetCurrentInstrument(), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("&Current Instrument")));
- AppendMenu(instrumentChangeMenu, MF_STRING, ID_PATTERN_SETINSTRUMENT, ih->GetKeyTextFromCommand(kcPatternSetInstrumentNotEmpty, _T("Current Instrument (&only change existing)")));
- }
- return BuildTogglePlugEditorCtxMenu(hMenu, ih);
- }
- return false;
- }
- // Context menu for Param Control notes
- bool CViewPattern::BuildPCNoteCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
- {
- return false;
- }
- const ModCommand &selStart = *sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
- if(!selStart.IsPcNote())
- {
- return false;
- }
- CString s;
- // Create sub menu for "change plugin"
- HMENU pluginChangeMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(pluginChangeMenu), ih->GetKeyTextFromCommand(kcPatternSetInstrument, _T("Change Plugin")));
- for(PLUGINDEX nPlg = 0; nPlg < MAX_MIXPLUGINS; nPlg++)
- {
- if(sndFile->m_MixPlugins[nPlg].pMixPlugin != nullptr)
- {
- s = MPT_CFORMAT("{}: {}")(mpt::cfmt::dec0<2>(nPlg + 1), mpt::ToCString(sndFile->m_MixPlugins[nPlg].GetName()));
- AppendMenu(pluginChangeMenu, MF_STRING | (((nPlg + 1) == selStart.instr) ? MF_CHECKED : 0), ID_CHANGE_INSTRUMENT + nPlg + 1, s);
- }
- }
- if(selStart.instr >= 1 && selStart.instr <= MAX_MIXPLUGINS)
- {
- const SNDMIXPLUGIN &plug = sndFile->m_MixPlugins[selStart.instr - 1];
- if(plug.pMixPlugin != nullptr)
- {
- // Create sub menu for "change plugin param"
- HMENU paramChangeMenu = ::CreatePopupMenu();
- AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(paramChangeMenu), _T("Change Plugin Parameter\t"));
- const PlugParamIndex curParam = selStart.GetValueVolCol(), nParams = plug.pMixPlugin->GetNumParameters();
- for(PlugParamIndex i = 0; i < nParams; i++)
- {
- AppendMenu(paramChangeMenu, MF_STRING | ((i == curParam) ? MF_CHECKED : 0), ID_CHANGE_PCNOTE_PARAM + i, plug.pMixPlugin->GetFormattedParamName(i));
- }
- }
- }
- return BuildTogglePlugEditorCtxMenu(hMenu, ih);
- }
- bool CViewPattern::BuildTogglePlugEditorCtxMenu(HMENU hMenu, CInputHandler *ih) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
- {
- return false;
- }
- PLUGINDEX plug = 0;
- const ModCommand &selStart = *sndFile->Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
- if(selStart.IsPcNote())
- {
- // PC Event
- plug = selStart.instr;
- } else if(selStart.instr > 0 && selStart.instr <= sndFile->GetNumInstruments()
- && sndFile->Instruments[selStart.instr] != nullptr
- && sndFile->Instruments[selStart.instr]->nMixPlug)
- {
- // Regular instrument
- plug = sndFile->Instruments[selStart.instr]->nMixPlug;
- }
- if(plug && plug <= MAX_MIXPLUGINS && sndFile->m_MixPlugins[plug - 1].pMixPlugin != nullptr)
- {
- AppendMenu(hMenu, MF_STRING, ID_PATTERN_EDIT_PCNOTE_PLUGIN, ih->GetKeyTextFromCommand(kcPatternEditPCNotePlugin, _T("Toggle Plugin &Editor")));
- return true;
- }
- return false;
- }
- // Returns an ordered list of all channels in which a given column type is selected.
- CHANNELINDEX CViewPattern::ListChansWhereColSelected(PatternCursor::Columns colType, std::vector<CHANNELINDEX> &chans) const
- {
- CHANNELINDEX startChan = m_Selection.GetStartChannel();
- CHANNELINDEX endChan = m_Selection.GetEndChannel();
- chans.clear();
- chans.reserve(endChan - startChan + 1);
- // Check in which channels this column is selected.
- // 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.
- for(CHANNELINDEX i = startChan; i <= endChan; i++)
- {
- if(m_Selection.ContainsHorizontal(PatternCursor(0, i, colType)))
- {
- chans.push_back(i);
- }
- }
- return static_cast<CHANNELINDEX>(chans.size());
- }
- // Check if a column type is selected on any channel in the current selection.
- bool CViewPattern::IsColumnSelected(PatternCursor::Columns colType) const
- {
- return m_Selection.ContainsHorizontal(PatternCursor(0, m_Selection.GetStartChannel(), colType))
- || m_Selection.ContainsHorizontal(PatternCursor(0, m_Selection.GetEndChannel(), colType));
- }
- // Check if the given interpolation type is actually possible in the current selection.
- bool CViewPattern::IsInterpolationPossible(PatternCursor::Columns colType) const
- {
- std::vector<CHANNELINDEX> validChans;
- ListChansWhereColSelected(colType, validChans);
- ROWINDEX startRow = m_Selection.GetStartRow();
- ROWINDEX endRow = m_Selection.GetEndRow();
- for(auto chn : validChans)
- {
- if(IsInterpolationPossible(startRow, endRow, chn, colType))
- {
- return true;
- }
- }
- return false;
- }
- // Check if the given interpolation type is actually possible in a given channel.
- bool CViewPattern::IsInterpolationPossible(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX chan, PatternCursor::Columns colType) const
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(startRow == endRow || sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
- return false;
- bool result = false;
- const ModCommand &startRowMC = *sndFile->Patterns[m_nPattern].GetpModCommand(startRow, chan);
- const ModCommand &endRowMC = *sndFile->Patterns[m_nPattern].GetpModCommand(endRow, chan);
- UINT startRowCmd, endRowCmd;
- if(colType == PatternCursor::effectColumn && (startRowMC.IsPcNote() || endRowMC.IsPcNote()))
- return true;
- switch(colType)
- {
- case PatternCursor::noteColumn:
- startRowCmd = startRowMC.note;
- endRowCmd = endRowMC.note;
- result = (startRowCmd == endRowCmd && startRowCmd != NOTE_NONE) // Interpolate between two identical notes or Cut / Fade / etc...
- || (startRowCmd != NOTE_NONE && endRowCmd == NOTE_NONE) // Fill in values from the first row
- || (startRowCmd == NOTE_NONE && endRowCmd != NOTE_NONE) // Fill in values from the last row
- || (ModCommand::IsNoteOrEmpty(startRowMC.note) && ModCommand::IsNoteOrEmpty(endRowMC.note) && !(startRowCmd == NOTE_NONE && endRowCmd == NOTE_NONE)); // Interpolate between two notes of which one may be empty
- break;
- case PatternCursor::instrColumn:
- startRowCmd = startRowMC.instr;
- endRowCmd = endRowMC.instr;
- result = startRowCmd != 0 || endRowCmd != 0;
- break;
- case PatternCursor::volumeColumn:
- startRowCmd = startRowMC.volcmd;
- endRowCmd = endRowMC.volcmd;
- result = (startRowCmd == endRowCmd && startRowCmd != VOLCMD_NONE) // Interpolate between two identical commands
- || (startRowCmd != VOLCMD_NONE && endRowCmd == VOLCMD_NONE) // Fill in values from the first row
- || (startRowCmd == VOLCMD_NONE && endRowCmd != VOLCMD_NONE); // Fill in values from the last row
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- startRowCmd = startRowMC.command;
- endRowCmd = endRowMC.command;
- result = (startRowCmd == endRowCmd && startRowCmd != CMD_NONE) // Interpolate between two identical commands
- || (startRowCmd != CMD_NONE && endRowCmd == CMD_NONE) // Fill in values from the first row
- || (startRowCmd == CMD_NONE && endRowCmd != CMD_NONE); // Fill in values from the last row
- break;
- default:
- result = false;
- }
- return result;
- }
- void CViewPattern::OnRButtonDblClk(UINT nFlags, CPoint point)
- {
- OnRButtonDown(nFlags, point);
- CModScrollView::OnRButtonDblClk(nFlags, point);
- }
- // Toggle pending mute status for channel from context menu.
- void CViewPattern::OnTogglePendingMuteFromClick()
- {
- TogglePendingMute(m_MenuCursor.GetChannel());
- }
- // Toggle pending solo status for channel from context menu.
- void CViewPattern::OnPendingSoloChnFromClick()
- {
- PendingSoloChn(m_MenuCursor.GetChannel());
- }
- // Set pending unmute status for all channels.
- void CViewPattern::OnPendingUnmuteAllChnFromClick()
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr)
- {
- GetSoundFile()->PatternTransitionChnUnmuteAll();
- InvalidateChannelsHeaders();
- }
- }
- // Toggle pending solo status for a channel.
- void CViewPattern::PendingSoloChn(CHANNELINDEX nChn)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr)
- {
- GetSoundFile()->PatternTranstionChnSolo(nChn);
- InvalidateChannelsHeaders();
- }
- }
- // Toggle pending mute status for a channel.
- void CViewPattern::TogglePendingMute(CHANNELINDEX nChn)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile != nullptr)
- {
- pSndFile->m_bChannelMuteTogglePending[nChn] = !pSndFile->m_bChannelMuteTogglePending[nChn];
- InvalidateChannelsHeaders();
- }
- }
- // Check if editing is enabled, and if it's not, prompt the user to enable editing.
- bool CViewPattern::IsEditingEnabled_bmsg()
- {
- if(IsEditingEnabled())
- return true;
- if(TrackerSettings::Instance().patternNoEditPopup)
- return false;
- HMENU hMenu;
- if((hMenu = ::CreatePopupMenu()) == nullptr)
- return false;
- CPoint pt = GetPointFromPosition(m_Cursor);
- // We add an mnemonic for an unbreakable space to avoid activating edit mode accidentally.
- AppendMenuW(hMenu, MF_STRING, IDC_PATTERN_RECORD, L"Editing (recording) is disabled;&\u00A0 click here to enable it.");
- ClientToScreen(&pt);
- ::TrackPopupMenu(hMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, m_hWnd, NULL);
- ::DestroyMenu(hMenu);
- return false;
- }
- // Show playback time at a given pattern position.
- void CViewPattern::OnShowTimeAtRow()
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr)
- {
- return;
- }
- CString msg;
- const auto &order = Order();
- ORDERINDEX currentOrder = GetCurrentOrder();
- if(currentOrder < order.size() && order[currentOrder] == m_nPattern)
- {
- const double t = pSndFile->GetPlaybackTimeAt(currentOrder, GetCurrentRow(), false, false);
- if(t < 0)
- msg.Format(_T("Unable to determine the time. Possible cause: No order %d, row %u found in play sequence."), currentOrder, GetCurrentRow());
- else
- {
- const uint32 minutes = static_cast<uint32>(t / 60.0);
- const double seconds = t - (minutes * 60);
- 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);
- }
- } else
- {
- 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);
- }
- Reporting::Notification(msg);
- }
- // Set up split keyboard
- void CViewPattern::SetSplitKeyboardSettings()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- CSplitKeyboardSettings dlg(CMainFrame::GetMainFrame(), pModDoc->GetSoundFile(), pModDoc->GetSplitKeyboardSettings());
- if(dlg.DoModal() == IDOK)
- {
- // Update split keyboard settings in other pattern views
- pModDoc->UpdateAllViews(NULL, SampleHint().Names());
- }
- }
- // Paste pattern data using the given paste mode.
- void CViewPattern::ExecutePaste(PatternClipboard::PasteModes mode)
- {
- if(IsEditingEnabled_bmsg() && PastePattern(m_nPattern, m_Selection.GetUpperLeft(), mode))
- {
- InvalidatePattern(false);
- SetFocus();
- }
- }
- // Show plugin editor for plugin assigned to PC Event at the cursor position.
- void CViewPattern::OnTogglePCNotePluginEditor()
- {
- CModDoc *pModDoc = GetDocument();
- if(pModDoc == nullptr)
- return;
- CSoundFile &sndFile = pModDoc->GetSoundFile();
- if(!sndFile.Patterns.IsValidPat(m_nPattern))
- return;
- const ModCommand &m = *sndFile.Patterns[m_nPattern].GetpModCommand(m_Selection.GetStartRow(), m_Selection.GetStartChannel());
- PLUGINDEX plug = 0;
- if(!m.IsPcNote())
- {
- // No PC note: Toggle instrument's plugin editor
- if(m.instr && m.instr <= sndFile.GetNumInstruments() && sndFile.Instruments[m.instr])
- {
- plug = sndFile.Instruments[m.instr]->nMixPlug;
- }
- } else
- {
- plug = m.instr;
- }
- if(plug > 0 && plug <= MAX_MIXPLUGINS)
- pModDoc->TogglePluginEditor(plug - 1);
- }
- // Get the active pattern's rows per beat, or, if they are not overriden, the song's default rows per beat.
- ROWINDEX CViewPattern::GetRowsPerBeat() const
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- return 0;
- if(!pSndFile->Patterns[m_nPattern].GetOverrideSignature())
- return pSndFile->m_nDefaultRowsPerBeat;
- else
- return pSndFile->Patterns[m_nPattern].GetRowsPerBeat();
- }
- // Get the active pattern's rows per measure, or, if they are not overriden, the song's default rows per measure.
- ROWINDEX CViewPattern::GetRowsPerMeasure() const
- {
- const CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- return 0;
- if(!pSndFile->Patterns[m_nPattern].GetOverrideSignature())
- return pSndFile->m_nDefaultRowsPerMeasure;
- else
- return pSndFile->Patterns[m_nPattern].GetRowsPerMeasure();
- }
- // Set instrument
- void CViewPattern::SetSelectionInstrument(const INSTRUMENTINDEX instr, bool setEmptyInstrument)
- {
- CSoundFile *pSndFile = GetSoundFile();
- if(pSndFile == nullptr || !pSndFile->Patterns.IsValidPat(m_nPattern))
- {
- return;
- }
- BeginWaitCursor();
- PrepareUndo(m_Selection, "Set Instrument");
- bool modified = false;
- ApplyToSelection([instr, setEmptyInstrument, &modified] (ModCommand &m, ROWINDEX, CHANNELINDEX)
- {
- // If a note or an instr is present on the row, do the change, if required.
- // Do not set instr if note and instr are both blank,
- // but set instr if note is a PC note and instr is blank.
- if(((setEmptyInstrument && (m.IsNote() || m.IsPcNote())) || m.instr != 0)
- && (m.instr != instr))
- {
- m.instr = static_cast<ModCommand::INSTR>(instr);
- modified = true;
- }
- });
- if(modified)
- {
- SetModified();
- InvalidatePattern();
- }
- EndWaitCursor();
- }
- // Select a whole beat (selectBeat = true) or measure.
- void CViewPattern::SelectBeatOrMeasure(bool selectBeat)
- {
- const ROWINDEX adjust = selectBeat ? GetRowsPerBeat() : GetRowsPerMeasure();
- // Snap to start of beat / measure of upper-left corner of current selection
- const ROWINDEX startRow = m_Selection.GetStartRow() - (m_Selection.GetStartRow() % adjust);
- // Snap to end of beat / measure of lower-right corner of current selection
- const ROWINDEX endRow = m_Selection.GetEndRow() + adjust - (m_Selection.GetEndRow() % adjust) - 1;
- CHANNELINDEX startChannel = m_Selection.GetStartChannel(), endChannel = m_Selection.GetEndChannel();
- PatternCursor::Columns startColumn = PatternCursor::firstColumn, endColumn = PatternCursor::firstColumn;
- if(m_Selection.GetUpperLeft() == m_Selection.GetLowerRight())
- {
- // No selection has been made yet => expand selection to whole channel.
- endColumn = PatternCursor::lastColumn; // Extend to param column
- } else if(startRow == m_Selection.GetStartRow() && endRow == m_Selection.GetEndRow())
- {
- // Whole beat or measure is already selected
- if(m_Selection.GetStartColumn() == PatternCursor::firstColumn && m_Selection.GetEndColumn() == PatternCursor::lastColumn)
- {
- // Whole channel is already selected => expand selection to whole row.
- startChannel = 0;
- startColumn = PatternCursor::firstColumn;
- endChannel = MAX_BASECHANNELS;
- endColumn = PatternCursor::lastColumn;
- } else
- {
- // Channel is only partly selected => expand to whole channel first.
- endColumn = PatternCursor::lastColumn; // Extend to param column
- }
- } else
- {
- // Some arbitrary selection: Remember start / end column
- startColumn = m_Selection.GetStartColumn();
- endColumn = m_Selection.GetEndColumn();
- }
- SetCurSel(PatternCursor(startRow, startChannel, startColumn), PatternCursor(endRow, endChannel, endColumn));
- }
- // Sweep pattern channel to find instrument number to use
- void CViewPattern::FindInstrument()
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr)
- {
- return;
- }
- const auto &order = Order();
- ORDERINDEX ord = GetCurrentOrder();
- PATTERNINDEX pat = m_nPattern;
- ROWINDEX row = m_Cursor.GetRow();
- while(sndFile->Patterns.IsValidPat(pat))
- {
- // Seek upwards
- do
- {
- auto &m = *sndFile->Patterns[pat].GetpModCommand(row, m_Cursor.GetChannel());
- if(!m.IsPcNote() && m.instr != 0)
- {
- SendCtrlMessage(CTRLMSG_SETCURRENTINSTRUMENT, m.instr);
- static_cast<CModControlView *>(CWnd::FromHandle(m_hWndCtrl))->InstrumentChanged(m.instr);
- return;
- }
- } while(row-- != 0);
- // Try previous pattern
- if(ord == 0)
- {
- return;
- }
- ord = order.GetPreviousOrderIgnoringSkips(ord);
- pat = order[ord];
- if(!sndFile->Patterns.IsValidPat(pat))
- {
- return;
- }
- row = sndFile->Patterns[pat].GetNumRows() - 1;
- }
- }
- // Find previous or next column entry (note, instrument, ...) on this channel
- void CViewPattern::JumpToPrevOrNextEntry(bool nextEntry, bool select)
- {
- const CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || GetCurrentOrder() >= Order().size())
- {
- return;
- }
- const auto &order = Order();
- ORDERINDEX ord = GetCurrentOrder();
- PATTERNINDEX pat = m_nPattern;
- CHANNELINDEX chn = m_Cursor.GetChannel();
- PatternCursor::Columns column = m_Cursor.GetColumnType();
- int32 row = m_Cursor.GetRow();
- int direction = nextEntry ? 1 : -1;
- row += direction; // Don't want to find the cell we're already in
- while(sndFile->Patterns.IsValidPat(pat))
- {
- while(sndFile->Patterns[pat].IsValidRow(row))
- {
- auto &m = *sndFile->Patterns[pat].GetpModCommand(row, chn);
- bool found;
- switch(column)
- {
- case PatternCursor::noteColumn:
- found = m.note != NOTE_NONE;
- break;
- case PatternCursor::instrColumn:
- found = m.instr != 0;
- break;
- case PatternCursor::volumeColumn:
- found = m.volcmd != VOLCMD_NONE;
- break;
- case PatternCursor::effectColumn:
- case PatternCursor::paramColumn:
- found = m.command != CMD_NONE;
- break;
- default:
- found = false;
- }
- if(found)
- {
- if(select)
- {
- CursorJump(static_cast<int>(row) - m_Cursor.GetRow(), false);
- } else
- {
- SetCurrentOrder(ord);
- SetCurrentPattern(pat, row);
- if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_PLAYNAVIGATEROW)
- {
- PatternStep(row);
- }
- }
- return;
- }
- row += direction;
- }
- // Continue search in prev/next pattern (unless we also select - selections cannot span multiple patterns)
- if(select)
- return;
- ORDERINDEX nextOrd = nextEntry ? order.GetNextOrderIgnoringSkips(ord) : order.GetPreviousOrderIgnoringSkips(ord);
- pat = order[nextOrd];
- if(nextOrd == ord || !sndFile->Patterns.IsValidPat(pat))
- return;
- ord = nextOrd;
- row = nextEntry ? 0 : (sndFile->Patterns[pat].GetNumRows() - 1);
- }
- }
- // Copy to clipboard
- bool CViewPattern::CopyPattern(PATTERNINDEX nPattern, const PatternRect &selection)
- {
- BeginWaitCursor();
- bool result = PatternClipboard::Copy(*GetSoundFile(), nPattern, selection);
- EndWaitCursor();
- PatternClipboardDialog::UpdateList();
- return result;
- }
- // Paste from clipboard
- bool CViewPattern::PastePattern(PATTERNINDEX nPattern, const PatternCursor &pastePos, PatternClipboard::PasteModes mode)
- {
- BeginWaitCursor();
- PatternEditPos pos;
- pos.pattern = nPattern;
- pos.row = pastePos.GetRow();
- pos.channel = pastePos.GetChannel();
- pos.order = GetCurrentOrder();
- PatternRect rect;
- const bool patternExisted = GetSoundFile()->Patterns.IsValidPat(nPattern);
- bool orderChanged = false;
- bool result = PatternClipboard::Paste(*GetSoundFile(), pos, mode, rect, orderChanged);
- EndWaitCursor();
- PatternHint updateHint = PatternHint(PATTERNINDEX_INVALID).Data();
- if(pos.pattern != nPattern)
- {
- // Multipaste: Switch to pasted pattern.
- SetCurrentPattern(pos.pattern);
- SetCurrentOrder(pos.order);
- }
- if(orderChanged || (patternExisted != GetSoundFile()->Patterns.IsValidPat(nPattern)))
- {
- updateHint.Names();
- GetDocument()->UpdateAllViews(nullptr, SequenceHint(GetSoundFile()->Order.GetCurrentSequenceIndex()).Data(), nullptr);
- }
- if(result)
- {
- SetCurSel(rect);
- GetDocument()->SetModified();
- GetDocument()->UpdateAllViews(nullptr, updateHint, nullptr);
- }
- return result;
- }
- template<typename Func>
- void CViewPattern::ApplyToSelection(Func func)
- {
- CSoundFile *sndFile = GetSoundFile();
- if(sndFile == nullptr || !sndFile->Patterns.IsValidPat(m_nPattern))
- return;
- auto &pattern = sndFile->Patterns[m_nPattern];
- m_Selection.Sanitize(pattern.GetNumRows(), pattern.GetNumChannels());
- const CHANNELINDEX startChn = m_Selection.GetStartChannel(), endChn = m_Selection.GetEndChannel();
- const ROWINDEX endRow = m_Selection.GetEndRow();
- for(ROWINDEX row = m_Selection.GetStartRow(); row <= endRow; row++)
- {
- ModCommand *m = pattern.GetpModCommand(row, startChn);
- for(CHANNELINDEX chn = startChn; chn <= endChn; chn++, m++)
- {
- func(*m, row, chn);
- }
- }
- }
- INT_PTR CViewPattern::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
- {
- CRect rect;
- const auto item = GetDragItem(point, rect);
- const auto value = item.Value();
- const CSoundFile &sndFile = *GetSoundFile();
- mpt::winstring text;
- switch(item.Type())
- {
- case DragItem::PatternHeader:
- {
- text = _T("Show Pattern Properties");
- auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(kcShowPatternProperties, 0);
- if(!keyText.IsEmpty())
- text += MPT_CFORMAT(" ({})")(keyText);
- break;
- }
- case DragItem::ChannelHeader:
- if(value < sndFile.GetNumChannels())
- {
- if(!sndFile.ChnSettings[value].szName.empty())
- text = MPT_TFORMAT("{}: {}")(value + 1, mpt::ToWin(sndFile.GetCharsetInternal(), sndFile.ChnSettings[value].szName));
- else
- text = MPT_TFORMAT("Channel {}")(value + 1);
- }
- break;
- case DragItem::PluginName:
- if(value < sndFile.GetNumChannels())
- {
- PLUGINDEX mixPlug = sndFile.ChnSettings[value].nMixPlugin;
- if(mixPlug && mixPlug <= MAX_MIXPLUGINS)
- text = MPT_TFORMAT("{}: {}")(mixPlug, mpt::ToWin(sndFile.m_MixPlugins[mixPlug - 1].GetName()));
- else
- text = _T("No Plugin");
- }
- break;
- }
- if(text.empty())
- return CScrollView::OnToolHitTest(point, pTI);
- pTI->hwnd = m_hWnd;
- pTI->uId = item.ToIntPtr();
- pTI->rect = rect;
- // MFC will free() the text
- TCHAR *textP = static_cast<TCHAR *>(calloc(text.size() + 1, sizeof(TCHAR)));
- std::copy(text.begin(), text.end(), textP);
- pTI->lpszText = textP;
- return item.ToIntPtr();
- }
- // Accessible description for screen readers
- HRESULT CViewPattern::get_accName(VARIANT varChild, BSTR *pszName)
- {
- const ModCommand &m = GetCursorCommand();
- const size_t columnIndex = m_Cursor.GetColumnType();
- const TCHAR *column = _T("");
- static constexpr const TCHAR *regularColumns[] = {_T("Note"), _T("Instrument"), _T("Volume"), _T("Effect"), _T("Parameter")};
- static constexpr const TCHAR *pcColumns[] = {_T("Note"), _T("Plugin"), _T("Plugin Parameter"), _T("Parameter Value"), _T("Parameter Value")};
- static_assert(PatternCursor::lastColumn + 1 == std::size(regularColumns));
- static_assert(PatternCursor::lastColumn + 1 == std::size(pcColumns));
- if(m.IsPcNote() && columnIndex < std::size(pcColumns))
- column = pcColumns[columnIndex];
- else if(!m.IsPcNote() && columnIndex < std::size(regularColumns))
- column = regularColumns[columnIndex];
- const CSoundFile *sndFile = GetSoundFile();
- const CHANNELINDEX chn = m_Cursor.GetChannel();
- const auto channelNumber = mpt::cfmt::val(chn + 1);
- CString channelName = channelNumber;
- if(chn < sndFile->GetNumChannels() && !sndFile->ChnSettings[chn].szName.empty())
- channelName += _T(": ") + mpt::ToCString(sndFile->GetCharsetInternal(), sndFile->ChnSettings[chn].szName);
- CString str = TrackerSettings::Instance().patternAccessibilityFormat;
- str.Replace(_T("%sequence%"), mpt::cfmt::val(sndFile->Order.GetCurrentSequenceIndex()));
- str.Replace(_T("%order%"), mpt::cfmt::val(GetCurrentOrder()));
- str.Replace(_T("%pattern%"), mpt::cfmt::val(GetCurrentPattern()));
- str.Replace(_T("%row%"), mpt::cfmt::val(m_Cursor.GetRow()));
- str.Replace(_T("%channel%"), channelNumber);
- str.Replace(_T("%column_type%"), column);
- str.Replace(_T("%column_description%"), GetCursorDescription());
- str.Replace(_T("%channel_name%"), channelName);
- if(str.IsEmpty())
- return CModScrollView::get_accName(varChild, pszName);
- *pszName = str.AllocSysString();
- return S_OK;
- }
- OPENMPT_NAMESPACE_END
|