aW1wb3J0IHRraW50ZXIgYXMgdGsKZnJvbSB0a2ludGVyIGltcG9ydCB0dGsKaW1wb3J0IG1hdGgKCmNsYXNzIFNvdW5kV2F2ZUFwcDoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCByb290KToKICAgICAgICBzZWxmLnJvb3QgPSByb290CiAgICAgICAgc2VsZi5yb290LnRpdGxlKCZxdW90O+yGjOumrOydmCAz7JqU7IaMIO2MjOuPmSDsi6Ttl5jsi6QgKOqzvO2VmSDsiJjtlontj4nqsIApJnF1b3Q7KQogICAgICAgIHNlbGYucm9vdC5nZW9tZXRyeSgmcXVvdDs5MDB4NjUwJnF1b3Q7KQogICAgICAgIHNlbGYucm9vdC5jb25maWd1cmUoYmc9JnF1b3Q7I2YwZjRmOCZxdW90OykKCiAgICAgICAgIyDrjbDsnbTthLAg7KCA7J6l7JqpIOuzgOyImAogICAgICAgIHNlbGYuYW1wbGl0dWRlID0gNTAgICAjIOy0iOq4sCDsp4Ttj60gKOyGjOumrCDtgazquLApCiAgICAgICAgc2VsZi5mcmVxdWVuY3kgPSAyICAgIyDstIjquLAg7KeE64+Z7IiYICjshozrpqwg64aS64Ku7J20KQogICAgICAgIHNlbGYuaW5zdHJ1bWVudCA9ICZxdW90O+yLpOuhnO2PsCZxdW90OyAjIOy0iOq4sCDslYXquLAgKOyGjOumrCDrp7Xsi5wpCiAgICAgICAgc2VsZi5jYXB0dXJlZF93YXZlcyA9IFtdICMg7Lqh7LKY65CcIO2MjOuPmSDsoIDsnqUg66as7Iqk7Yq4CgogICAgICAgIHNlbGYuc2V0dXBfdWkoKQogICAgICAgIHNlbGYuZHJhd193YXZlKCkKCiAgICBkZWYgc2V0dXBfdWkoc2VsZik6CiAgICAgICAgIyAtLS0g7IOB64uoIO2DgOydtO2LgCAtLS0KICAgICAgICB0aXRsZV9sYWJlbCA9IHRrLkxhYmVsKHNlbGYucm9vdCwgdGV4dD0mcXVvdDvwn5SKIOyGjOumrOydmCDshLHsp4gg67CPIO2MjOuPmSDruYTqtZAg7ZSE66Gc6re4656oJnF1b3Q7LCBmb250PSgmcXVvdDvrp5HsnYAg6rOg65SVJnF1b3Q7LCAxOCwgJnF1b3Q7Ym9sZCZxdW90OyksIGJnPSZxdW90OyNmMGY0ZjgmcXVvdDssIGZnPSZxdW90OyMzMzMzMzMmcXVvdDspCiAgICAgICAgdGl0bGVfbGFiZWwucGFjayhwYWR5PTEwKQoKICAgICAgICAjIOuplOyduCDtlITroIjsnoQgKOyijOyasCDrtoTtlaApCiAgICAgICAgbWFpbl9mcmFtZSA9IHRrLkZyYW1lKHNlbGYucm9vdCwgYmc9JnF1b3Q7I2YwZjRmOCZxdW90OykKICAgICAgICBtYWluX2ZyYW1lLnBhY2soZmlsbD10ay5CT1RILCBleHBhbmQ9VHJ1ZSwgcHg9MTAsIHB5PTUpCgogICAgICAgICMgLS0tIOyZvOyqvSDtlITroIjsnoQ6IOyhsOyekSDrsI8g7Iuk7Iuc6rCEIOq3uOuemO2UhCAtLS0KICAgICAgICBsZWZ0X2ZyYW1lID0gdGsuRnJhbWUobWFpbl9mcmFtZSwgYmc9JnF1b3Q7I2YwZjRmOCZxdW90OykKICAgICAgICBsZWZ0X2ZyYW1lLnBhY2soc2lkZT10ay5MRUZULCBmaWxsPXRrLkJPVEgsIGV4cGFuZD1UcnVlLCBweD0xMCkKCiAgICAgICAgIyAxLiDsi6Tsi5zqsIQg6re4656Y7ZSEIOy6lOuyhOyKpAogICAgICAgIHNlbGYuY2FudmFzID0gdGsuQ2FudmFzKGxlZnRfZnJhbWUsIHdpZHRoPTUwMCwgaGVpZ2h0PTI1MCwgYmc9JnF1b3Q7d2hpdGUmcXVvdDssIGhpZ2hsaWdodHRoaWNrbmVzcz0xLCBoaWdobGlnaHRiYWNrZ3JvdW5kPSZxdW90OyNjY2NjY2MmcXVvdDspCiAgICAgICAgc2VsZi5jYW52YXMucGFjayhwYWR5PTUpCgogICAgICAgICMgMi4g7KCc7Ja07YyQIO2UhOugiOyehAogICAgICAgIGNvbnRyb2xfZnJhbWUgPSB0ay5MYWJlbEZyYW1lKGxlZnRfZnJhbWUsIHRleHQ9JnF1b3Q78J+Om++4jyDtjIzrj5kg7KCc7Ja07YyQJnF1b3Q7LCBmb250PSgmcXVvdDvrp5HsnYAg6rOg65SVJnF1b3Q7LCAxMSwgJnF1b3Q7Ym9sZCZxdW90OyksIGJnPSZxdW90OyNmZmZmZmYmcXVvdDssIHB4PTE1LCBweT0xMCkKICAgICAgICBjb250cm9sX2ZyYW1lLnBhY2soZmlsbD10ay5YLCBwYWR5PTEwKQoKICAgICAgICAjIOyVheq4sCDshKDtg50gKOyGjOumrOydmCDrp7Xsi5wgLSDtjIztmJUg6rKw7KCVKQogICAgICAgIHRrLkxhYmVsKGNvbnRyb2xfZnJhbWUsIHRleHQ9JnF1b3Q78J+OtSDslYXquLAg7ISg7YOdICjsnYzsg4kv7YyM7ZiVKTomcXVvdDssIGJnPSZxdW90OyNmZmZmZmYmcXVvdDssIGZvbnQ9KCZxdW90O+unkeydgCDqs6DrlJUmcXVvdDssIDEwKSkuZ3JpZChyb3c9MCwgY29sdW1uPTAsIHN0aWNreT0mcXVvdDt3JnF1b3Q7LCBwYWR5PTUpCiAgICAgICAgc2VsZi5pbnN0X2NvbWJvID0gdHRrLkNvbWJvYm94KGNvbnRyb2xfZnJhbWUsIHZhbHVlcz1bJnF1b3Q77Iuk66Gc7Y+wICjsoJXtmITtjIwpJnF1b3Q7LCAmcXVvdDvrpqzsvZTrjZQgKOuzte2Vqe2MjCkmcXVvdDssICZxdW90O+uTnOufvCAo66el64+Z7YyMKSZxdW90O10sIHN0YXRlPSZxdW90O3JlYWRvbmx5JnF1b3Q7LCB3aWR0aD0xOCkKICAgICAgICBzZWxmLmluc3RfY29tYm8uY3VycmVudCgwKQogICAgICAgIHNlbGYuaW5zdF9jb21iby5ncmlkKHJvdz0wLCBjb2x1bW49MSwgc3RpY2t5PSZxdW90O3cmcXVvdDssIHBhZHk9NSkKICAgICAgICBzZWxmLmluc3RfY29tYm8uYmluZCgmcXVvdDsmbHQ7Jmx0O0NvbWJvYm94U2VsZWN0ZWQmZ3Q7Jmd0OyZxdW90Oywgc2VsZi5vbl9pbnN0X2NoYW5nZSkKCiAgICAgICAgIyDshozrpqwg7IS46riwIOyhsOygiCAo7KeE7Y+tKQogICAgICAgIHRrLkxhYmVsKGNvbnRyb2xfZnJhbWUsIHRleHQ9JnF1b3Q78J+UiiDshozrpqwg7IS46riwICjsp4Ttj60pOiZxdW90OywgYmc9JnF1b3Q7I2ZmZmZmZiZxdW90OywgZm9udD0oJnF1b3Q766eR7J2AIOqzoOuUlSZxdW90OywgMTApKS5ncmlkKHJvdz0xLCBjb2x1bW49MCwgc3RpY2t5PSZxdW90O3cmcXVvdDssIHBhZHk9NSkKICAgICAgICBzZWxmLmFtcF9zbGlkZXIgPSB0ay5TY2FsZShjb250cm9sX2ZyYW1lLCBmcm9tXz0xMCwgdG89MTAwLCBvcmllbnRhdGlvbj10ay5IT1JJWk9OVEFMLCBiZz0mcXVvdDsjZmZmZmZmJnF1b3Q7LCBoaWdobGlnaHR0aGlja25lc3M9MCwgY29tbWFuZD1zZWxmLm9uX2FtcF9jaGFuZ2UpCiAgICAgICAgc2VsZi5hbXBfc2xpZGVyLnNldChzZWxmLmFtcGxpdHVkZSkKICAgICAgICBzZWxmLmFtcF9zbGlkZXIuZ3JpZChyb3c9MSwgY29sdW1uPTEsIGZpbGw9dGsuWCwgcGFkeT01KQoKICAgICAgICAjIOyGjOumrCDrhpLrgq7snbQg7KGw7KCIICjsp4Trj5nsiJgpCiAgICAgICAgdGsuTGFiZWwoY29udHJvbF9mcmFtZSwgdGV4dD0mcXVvdDvwn468IOyGjOumrCDrhpLrgq7snbQgKOynhOuPmeyImCk6JnF1b3Q7LCBiZz0mcXVvdDsjZmZmZmZmJnF1b3Q7LCBmb250PSgmcXVvdDvrp5HsnYAg6rOg65SVJnF1b3Q7LCAxMCkpLmdyaWQocm93PTIsIGNvbHVtbj0wLCBzdGlja3k9JnF1b3Q7dyZxdW90OywgcGFkeT01KQogICAgICAgIHNlbGYuZnJlcV9zbGlkZXIgPSB0ay5TY2FsZShjb250cm9sX2ZyYW1lLCBmcm9tXz0xLCB0bz01LCByZXNvbHV0aW9uPTAuNSwgb3JpZW50YXRpb249dGsuSE9SSVpPTlRBTCwgYmc9JnF1b3Q7I2ZmZmZmZiZxdW90OywgaGlnaGxpZ2h0dGhpY2tuZXNzPTAsIGNvbW1hbmQ9c2VsZi5vbl9mcmVxX2NoYW5nZSkKICAgICAgICBzZWxmLmZyZXFfc2xpZGVyLnNldChzZWxmLmZyZXF1ZW5jeSkKICAgICAgICBzZWxmLmZyZXFfc2xpZGVyLmdyaWQocm93PTIsIGNvbHVtbj0xLCBmaWxsPXRrLlgsIHBhZHk9NSkKCiAgICAgICAgIyDsuqHsspgg67KE7Yq8CiAgICAgICAgY2FwdHVyZV9idG4gPSB0ay5CdXR0b24obGVmdF9mcmFtZSwgdGV4dD0mcXVvdDvwn5O4IO2YhOyerCDtjIzrj5kg7Lqh7LKY7ZWY6riwJnF1b3Q7LCBmb250PSgmcXVvdDvrp5HsnYAg6rOg65SVJnF1b3Q7LCAxMSwgJnF1b3Q7Ym9sZCZxdW90OyksIGJnPSZxdW90OyM0Q0FGNTAmcXVvdDssIGZnPSZxdW90O3doaXRlJnF1b3Q7LCByZXBseT0xMCwgY29tbWFuZD1zZWxmLmNhcHR1cmVfd2F2ZSkKICAgICAgICBjYXB0dXJlX2J0bi5wYWNrKGZpbGw9dGsuWCwgcGFkeT01KQoKCiAgICAgICAgIyAtLS0g7Jik66W47Kq9IO2UhOugiOyehDog7Lqh7LKYIOumrOyKpO2KuCDrsI8g7KSR7LKpIOu5hOq1kCDqt7jrnpjtlIQgLS0tCiAgICAgICAgcmlnaHRfZnJhbWUgPSB0ay5GcmFtZShtYWluX2ZyYW1lLCBiZz0mcXVvdDsjZjBmNGY4JnF1b3Q7KQogICAgICAgIHJpZ2h0X2ZyYW1lLnBhY2soc2lkZT10ay5SSUdIVCwgZmlsbD10ay5CT1RILCBleHBhbmQ9VHJ1ZSwgcHg9MTApCgogICAgICAgICMgMS4g7KSR7LKpIOu5hOq1kCDqt7jrnpjtlIQg7LqU67KE7IqkCiAgICAgICAgdGsuTGFiZWwocmlnaHRfZnJhbWUsIHRleHQ9JnF1b3Q78J+UhCDtjIzrj5kg7KSR7LKpIOu5hOq1kCAo6rK57LOQ67O06riwKSZxdW90OywgZm9udD0oJnF1b3Q766eR7J2AIOqzoOuUlSZxdW90OywgMTEsICZxdW90O2JvbGQmcXVvdDspLCBiZz0mcXVvdDsjZjBmNGY4JnF1b3Q7KS5wYWNrKGFuY2hvcj0mcXVvdDt3JnF1b3Q7KQogICAgICAgIHNlbGYuY29tcGFyZV9jYW52YXMgPSB0ay5DYW52YXMocmlnaHRfZnJhbWUsIHdpZHRoPTMyMCwgaGVpZ2h0PTIwMCwgYmc9JnF1b3Q7IzIyMjIyMiZxdW90OywgaGlnaGxpZ2h0dGhpY2tuZXNzPTEpCiAgICAgICAgc2VsZi5jb21wYXJlX2NhbnZhcy5wYWNrKHBhZHk9NSkKCiAgICAgICAgIyAyLiDsuqHsspjrkJwg6riw66GdIOumrOyKpO2KuOuwleyKpAogICAgICAgIHRrLkxhYmVsKHJpZ2h0X2ZyYW1lLCB0ZXh0PSZxdW90O/Cfk4sg7Lqh7LKY65CcIO2MjOuPmSDrqqnroZ0gKOy1nOuMgCAz6rCcKSZxdW90OywgZm9udD0oJnF1b3Q766eR7J2AIOqzoOuUlSZxdW90OywgMTEsICZxdW90O2JvbGQmcXVvdDspLCBiZz0mcXVvdDsjZjBmNGY4JnF1b3Q7KS5wYWNrKGFuY2hvcj0mcXVvdDt3JnF1b3Q7LCBwYWR5PSgxMCwgMCkpCiAgICAgICAgc2VsZi5oaXN0b3J5X2JveCA9IHRrLkxpc3Rib3gocmlnaHRfZnJhbWUsIGhlaWdodD02LCBmb250PSgmcXVvdDvrp5HsnYAg6rOg65SVJnF1b3Q7LCAxMCkpCiAgICAgICAgc2VsZi5oaXN0b3J5X2JveC5wYWNrKGZpbGw9dGsuQk9USCwgZXhwYW5kPVRydWUsIHBhZHk9NSkKCiAgICAgICAgIyDstIjquLDtmZQg67KE7Yq8CiAgICAgICAgcmVzZXRfYnRuID0gdGsuQnV0dG9uKHJpZ2h0X2ZyYW1lLCB0ZXh0PSZxdW90O/Cfl5HvuI8g7Lqh7LKYIOy0iOq4sO2ZlCZxdW90OywgZm9udD0oJnF1b3Q766eR7J2AIOqzoOuUlSZxdW90OywgMTApLCBiZz0mcXVvdDsjZjQ0MzM2JnF1b3Q7LCBmZz0mcXVvdDt3aGl0ZSZxdW90OywgY29tbWFuZD1zZWxmLnJlc2V0X2NhcHR1cmVzKQogICAgICAgIHJlc2V0X2J0bi5wYWNrKGZpbGw9dGsuWCwgcGFkeT01KQoKICAgICMg7YyM64+ZIOyImO2VmeyggSDqs4TsgrAg67CPIOq3uOumrOq4sAogICAgZGVmIGNhbGN1bGF0ZV93YXZlX3BvaW50cyhzZWxmLCBhbXAsIGZyZXEsIGluc3QsIHdpZHRoLCBoZWlnaHQpOgogICAgICAgIHBvaW50cyA9IFtdCiAgICAgICAgY2VudGVyX3kgPSBoZWlnaHQgLyAyCiAgICAgICAgCiAgICAgICAgZm9yIHggaW4gcmFuZ2UoMCwgd2lkdGgsIDIpOgogICAgICAgICAgICAjIOq4sOuzuCDsp4Trj5nsi50gKOyLpOuhnO2PsDog6rmo64GX7ZWcIOyCrOyduO2MjCkKICAgICAgICAgICAgdGhldGEgPSAoeCAvIHdpZHRoKSAqIDIgKiBtYXRoLnBpICogZnJlcQogICAgICAgICAgICB5X29mZnNldCA9IG1hdGguc2luKHRoZXRhKQogICAgICAgICAgICAKICAgICAgICAgICAgIyDslYXquLDrs4Qg7YyM7ZiVKOunteyLnCkg67OA7ZiVCiAgICAgICAgICAgIGlmICZxdW90O+umrOy9lOuNlCZxdW90OyBpbiBpbnN0OgogICAgICAgICAgICAgICAgIyDqs6DsobDtjIzrpbwg7ISe7Ja0IOu+sOyhse2VnCDrs7XtlantjIwg7IOd7ISxCiAgICAgICAgICAgICAgICB5X29mZnNldCArPSAwLjMgKiBtYXRoLnNpbigzICogdGhldGEpCiAgICAgICAgICAgIGVsaWYgJnF1b3Q765Oc65+8JnF1b3Q7IGluIGluc3Q6CiAgICAgICAgICAgICAgICAjIOyngOyImO2VqOyImOulvCDqs7HtlbQg7IaM66as6rCAIOqwkOyHhO2VmOuKlCDtmJXtg5wg7ZGc7ZiECiAgICAgICAgICAgICAgICB5X29mZnNldCA9IG1hdGguc2luKHRoZXRhICogMS41KSAqIG1hdGguZXhwKC14IC8gKHdpZHRoICogMC42KSkKICAgICAgICAgICAgCiAgICAgICAgICAgIHkgPSBjZW50ZXJfeSAtICh5X29mZnNldCAqIGFtcCkKICAgICAgICAgICAgcG9pbnRzLmFwcGVuZCgoeCwgeSkpCiAgICAgICAgcmV0dXJuIHBvaW50cwoKICAgIGRlZiBkcmF3X3dhdmUoc2VsZik6CiAgICAgICAgIyAxLiDsi6Tsi5zqsIQg7LqU67KE7IqkIOq3uOumrOq4sAogICAgICAgIHNlbGYuY2FudmFzLmRlbGV0ZSgmcXVvdDthbGwmcXVvdDspCiAgICAgICAgdywgaCA9IDUwMCwgMjUwCiAgICAgICAgCiAgICAgICAgIyDrqqjriIjsooXsnbQg6rCA7J2065Oc65287J24CiAgICAgICAgc2VsZi5jYW52YXMuY3JlYXRlX2xpbmUoMCwgaC8yLCB3LCBoLzIsIGZpbGw9JnF1b3Q7I2FhYWFhYSZxdW90OywgZGFzaD0oNCwgNCkpCiAgICAgICAgZm9yIHggaW4gcmFuZ2UoMCwgdywgNDApOgogICAgICAgICAgICBzZWxmLmNhbnZhcy5jcmVhdGVfbGluZSh4LCAwLCB4LCBoLCBmaWxsPSZxdW90OyNmMGYwZjAmcXVvdDspCiAgICAgICAgZm9yIHkgaW4gcmFuZ2UoMCwgaCwgNDApOgogICAgICAgICAgICBzZWxmLmNhbnZhcy5jcmVhdGVfbGluZSgwLCB5LCB3LCB5LCBmaWxsPSZxdW90OyNmMGYwZjAmcXVvdDspCgogICAgICAgICMg7YyM64+Z7ISgIOq3uOumrOq4sAogICAgICAgIHBvaW50cyA9IHNlbGYuY2FsY3VsYXRlX3dhdmVfcG9pbnRzKHNlbGYuYW1wbGl0dWRlLCBzZWxmLmZyZXF1ZW5jeSwgc2VsZi5pbnN0cnVtZW50LCB3LCBoKQogICAgICAgIGZvciBpIGluIHJhbmdlKGxlbihwb2ludHMpIC0gMSk6CiAgICAgICAgICAgIHNlbGYuY2FudmFzLmNyZWF0ZV9saW5lKHBvaW50c1tpXVswXSwgcG9pbnRzW2ldWzFdLCBwb2ludHNbaSsxXVswXSwgcG9pbnRzW2krMV1bMV0sIGZpbGw9JnF1b3Q7IzAwN0JGRiZxdW90Oywgd2lkdGg9MykKICAgICAgICAgICAgCiAgICAgICAgIyDsg4Htg5wg7YWN7Iqk7Yq4IO2RnOyLnAogICAgICAgIGluZm9fdGV4dCA9IGYmcXVvdDvshKDtg53rkJwg7JWF6riwOiB7c2VsZi5pbnN0cnVtZW50fSB8IOynhO2PrSjtgazquLApOiB7c2VsZi5hbXBsaXR1ZGV9IHwg7KeE64+Z7IiYKOuGkuuCruydtCk6IHtzZWxmLmZyZXF1ZW5jeX1IeiZxdW90OwogICAgICAgIHNlbGYuY2FudmFzLmNyZWF0ZV90ZXh0KDE1LCBoIC0gMTUsIHRleHQ9aW5mb190ZXh0LCBhbmNob3I9JnF1b3Q7dyZxdW90OywgZm9udD0oJnF1b3Q766eR7J2AIOqzoOuUlSZxdW90OywgMTAsICZxdW90O2JvbGQmcXVvdDspLCBmaWxsPSZxdW90OyMzMzMzMzMmcXVvdDspCgogICAgZGVmIGRyYXdfY29tcGFyZV9jYW52YXMoc2VsZik6CiAgICAgICAgIyAyLiDspJHssqkg7LqU67KE7IqkIOq3uOumrOq4sAogICAgICAgIHNlbGYuY29tcGFyZV9jYW52YXMuZGVsZXRlKCZxdW90O2FsbCZxdW90OykKICAgICAgICB3LCBoID0gMzIwLCAyMDAKICAgICAgICBzZWxmLmNvbXBhcmVfY2FudmFzLmNyZWF0ZV9saW5lKDAsIGgvMiwgdywgaC8yLCBmaWxsPSZxdW90OyM1NTU1NTUmcXVvdDssIGRhc2g9KDIsIDIpKQoKICAgICAgICBjb2xvcnMgPSBbJnF1b3Q7I0ZGNTcyMiZxdW90OywgJnF1b3Q7IzRDQUY1MCZxdW90OywgJnF1b3Q7I0U5MUU2MyZxdW90O10gIyDsuqHsspgg7YyM64+ZIOyDieyDgSAo7KO87ZmpLCDstIjroZ0sIO2Vke2BrCkKICAgICAgICAKICAgICAgICBmb3IgaWR4LCB3YXZlIGluIGVudW1lcmF0ZShzZWxmLmNhcHR1cmVkX3dhdmVzKToKICAgICAgICAgICAgcHRzID0gc2VsZi5jYWxjdWxhdGVfd2F2ZV9wb2ludHMod2F2ZVsnYW1wJ10sIHdhdmVbJ2ZyZXEnXSwgd2F2ZVsnaW5zdCddLCB3LCBoKQogICAgICAgICAgICBmb3IgaSBpbiByYW5nZShsZW4ocHRzKSAtIDEpOgogICAgICAgICAgICAgICAgc2VsZi5jb21wYXJlX2NhbnZhcy5jcmVhdGVfbGluZShwdHNbaV1bMF0sIHB0c1tpXVsxXSwgcHRzW2krMV1bMF0sIHB0c1tpKzFdWzFdLCBmaWxsPWNvbG9yc1tpZHhdLCB3aWR0aD0yKQoKICAgICMg7J2067Kk7Yq4IO2VqOyImOuTpAogICAgZGVmIG9uX2FtcF9jaGFuZ2Uoc2VsZiwgdmFsKToKICAgICAgICBzZWxmLmFtcGxpdHVkZSA9IGludCh2YWwpCiAgICAgICAgc2VsZi5kcmF3X3dhdmUoKQoKICAgIGRlZiBvbl9mcmVxX2NoYW5nZShzZWxmLCB2YWwpOgogICAgICAgIHNlbGYuZnJlcXVlbmN5ID0gZmxvYXQodmFsKQogICAgICAgIHNlbGYuZHJhd193YXZlKCkKCiAgICBkZWYgb25faW5zdF9jaGFuZ2Uoc2VsZiwgZXZlbnQpOgogICAgICAgIHNlbGYuaW5zdHJ1bWVudCA9IHNlbGYuaW5zdF9jb21iby5nZXQoKQogICAgICAgIHNlbGYuZHJhd193YXZlKCkKCiAgICBkZWYgY2FwdHVyZV93YXZlKHNlbGYpOgogICAgICAgIGlmIGxlbihzZWxmLmNhcHR1cmVkX3dhdmVzKSAmZ3Q7PSAzOgogICAgICAgICAgICBzZWxmLmNhcHR1cmVkX3dhdmVzLnBvcCgwKSAjIDPqsJwg64SY7Jy866m0IOygnOydvCDsmKTrnpjrkJwg6rKDIOyCreygnAogICAgICAgICAgICBzZWxmLmhpc3RvcnlfYm94LmRlbGV0ZSgwKQoKICAgICAgICAjIO2YhOyerCDsg4Htg5wg7KCA7J6lCiAgICAgICAgd2F2ZV9kYXRhID0gewogICAgICAgICAgICAnaW5zdCc6IHNlbGYuaW5zdHJ1bWVudCwKICAgICAgICAgICAgJ2FtcCc6IHNlbGYuYW1wbGl0dWRlLAogICAgICAgICAgICAnZnJlcSc6IHNlbGYuZnJlcXVlbmN5CiAgICAgICAgfQogICAgICAgIHNlbGYuY2FwdHVyZWRfd2F2ZXMuYXBwZW5kKHdhdmVfZGF0YSkKICAgICAgICAKICAgICAgICAjIOumrOyKpO2KuOuwleyKpOyXkCDtkZzsi5wKICAgICAgICBjb2xvcnNfdGV4dCA9IFsmcXVvdDvwn5S0JnF1b3Q7LCAmcXVvdDvwn5+iJnF1b3Q7LCAmcXVvdDvwn5S1JnF1b3Q7XQogICAgICAgIGlkeCA9IGxlbihzZWxmLmNhcHR1cmVkX3dhdmVzKSAtIDEKICAgICAgICBkaXNwbGF5X3RleHQgPSBmJnF1b3Q7e2NvbG9yc190ZXh0W2lkeF19IFt7d2F2ZV9kYXRhWydpbnN0J10uc3BsaXQoKVswXX1dIOynhO2PrTp7d2F2ZV9kYXRhWydhbXAnXX0gLyDsp4Trj5nsiJg6e3dhdmVfZGF0YVsnZnJlcSddfSZxdW90OwogICAgICAgIHNlbGYuaGlzdG9yeV9ib3guaW5zZXJ0KHRrLkVORCwgZGlzcGxheV90ZXh0KQogICAgICAgIAogICAgICAgICMg7KSR7LKpIOq3uOuemO2UhCDsl4XrjbDsnbTtirgKICAgICAgICBzZWxmLmRyYXdfY29tcGFyZV9jYW52YXMoKQoKICAgIGRlZiByZXNldF9jYXB0dXJlcyhzZWxmKToKICAgICAgICBzZWxmLmNhcHR1cmVkX3dhdmVzID0gW10KICAgICAgICBzZWxmLmhpc3RvcnlfYm94LmRlbGV0ZSgwLCB0ay5FTkQpCiAgICAgICAgc2VsZi5jb21wYXJlX2NhbnZhcy5kZWxldGUoJnF1b3Q7YWxsJnF1b3Q7KQoKaWYgX19uYW1lX18gPT0gJnF1b3Q7X19tYWluX18mcXVvdDs6CiAgICByb290ID0gdGsuVGsoKQogICAgYXBwID0gU291bmRXYXZlQXBwKHJvb3QpCiAgICByb290Lm1haW5sb29wKCkK
import tkinter as tk
from tkinter import ttk
import math
class SoundWaveApp:
def __init__(self, root):
self.root = root
self.root.title("소리의 3요소 파동 실험실 (과학 수행평가)")
self.root.geometry("900x650")
self.root.configure(bg="#f0f4f8")
# 데이터 저장용 변수
self.amplitude = 50 # 초기 진폭 (소리 크기)
self.frequency = 2 # 초기 진동수 (소리 높낮이)
self.instrument = "실로폰" # 초기 악기 (소리 맵시)
self.captured_waves = [] # 캡처된 파동 저장 리스트
self.setup_ui()
self.draw_wave()
def setup_ui(self):
# --- 상단 타이틀 ---
title_label = tk.Label(self.root, text="🔊 소리의 성질 및 파동 비교 프로그램", font=("맑은 고딕", 18, "bold"), bg="#f0f4f8", fg="#333333")
title_label.pack(pady=10)
# 메인 프레임 (좌우 분할)
main_frame = tk.Frame(self.root, bg="#f0f4f8")
main_frame.pack(fill=tk.BOTH, expand=True, px=10, py=5)
# --- 왼쪽 프레임: 조작 및 실시간 그래프 ---
left_frame = tk.Frame(main_frame, bg="#f0f4f8")
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, px=10)
# 1. 실시간 그래프 캔버스
self.canvas = tk.Canvas(left_frame, width=500, height=250, bg="white", highlightthickness=1, highlightbackground="#cccccc")
self.canvas.pack(pady=5)
# 2. 제어판 프레임
control_frame = tk.LabelFrame(left_frame, text="🎛️ 파동 제어판", font=("맑은 고딕", 11, "bold"), bg="#ffffff", px=15, py=10)
control_frame.pack(fill=tk.X, pady=10)
# 악기 선택 (소리의 맵시 - 파형 결정)
tk.Label(control_frame, text="🎵 악기 선택 (음색/파형):", bg="#ffffff", font=("맑은 고딕", 10)).grid(row=0, column=0, sticky="w", pady=5)
self.inst_combo = ttk.Combobox(control_frame, values=["실로폰 (정현파)", "리코더 (복합파)", "드럼 (맥동파)"], state="readonly", width=18)
self.inst_combo.current(0)
self.inst_combo.grid(row=0, column=1, sticky="w", pady=5)
self.inst_combo.bind("<<ComboboxSelected>>", self.on_inst_change)
# 소리 세기 조절 (진폭)
tk.Label(control_frame, text="🔊 소리 세기 (진폭):", bg="#ffffff", font=("맑은 고딕", 10)).grid(row=1, column=0, sticky="w", pady=5)
self.amp_slider = tk.Scale(control_frame, from_=10, to=100, orientation=tk.HORIZONTAL, bg="#ffffff", highlightthickness=0, command=self.on_amp_change)
self.amp_slider.set(self.amplitude)
self.amp_slider.grid(row=1, column=1, fill=tk.X, pady=5)
# 소리 높낮이 조절 (진동수)
tk.Label(control_frame, text="🎼 소리 높낮이 (진동수):", bg="#ffffff", font=("맑은 고딕", 10)).grid(row=2, column=0, sticky="w", pady=5)
self.freq_slider = tk.Scale(control_frame, from_=1, to=5, resolution=0.5, orientation=tk.HORIZONTAL, bg="#ffffff", highlightthickness=0, command=self.on_freq_change)
self.freq_slider.set(self.frequency)
self.freq_slider.grid(row=2, column=1, fill=tk.X, pady=5)
# 캡처 버튼
capture_btn = tk.Button(left_frame, text="📸 현재 파동 캡처하기", font=("맑은 고딕", 11, "bold"), bg="#4CAF50", fg="white", reply=10, command=self.capture_wave)
capture_btn.pack(fill=tk.X, pady=5)
# --- 오른쪽 프레임: 캡처 리스트 및 중첩 비교 그래프 ---
right_frame = tk.Frame(main_frame, bg="#f0f4f8")
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, px=10)
# 1. 중첩 비교 그래프 캔버스
tk.Label(right_frame, text="🔄 파동 중첩 비교 (겹쳐보기)", font=("맑은 고딕", 11, "bold"), bg="#f0f4f8").pack(anchor="w")
self.compare_canvas = tk.Canvas(right_frame, width=320, height=200, bg="#222222", highlightthickness=1)
self.compare_canvas.pack(pady=5)
# 2. 캡처된 기록 리스트박스
tk.Label(right_frame, text="📋 캡처된 파동 목록 (최대 3개)", font=("맑은 고딕", 11, "bold"), bg="#f0f4f8").pack(anchor="w", pady=(10, 0))
self.history_box = tk.Listbox(right_frame, height=6, font=("맑은 고딕", 10))
self.history_box.pack(fill=tk.BOTH, expand=True, pady=5)
# 초기화 버튼
reset_btn = tk.Button(right_frame, text="🗑️ 캡처 초기화", font=("맑은 고딕", 10), bg="#f44336", fg="white", command=self.reset_captures)
reset_btn.pack(fill=tk.X, pady=5)
# 파동 수학적 계산 및 그리기
def calculate_wave_points(self, amp, freq, inst, width, height):
points = []
center_y = height / 2
for x in range(0, width, 2):
# 기본 진동식 (실로폰: 깨끗한 사인파)
theta = (x / width) * 2 * math.pi * freq
y_offset = math.sin(theta)
# 악기별 파형(맵시) 변형
if "리코더" in inst:
# 고조파를 섞어 뾰족한 복합파 생성
y_offset += 0.3 * math.sin(3 * theta)
elif "드럼" in inst:
# 지수함수를 곱해 소리가 감쇄하는 형태 표현
y_offset = math.sin(theta * 1.5) * math.exp(-x / (width * 0.6))
y = center_y - (y_offset * amp)
points.append((x, y))
return points
def draw_wave(self):
# 1. 실시간 캔버스 그리기
self.canvas.delete("all")
w, h = 500, 250
# 모눈종이 가이드라인
self.canvas.create_line(0, h/2, w, h/2, fill="#aaaaaa", dash=(4, 4))
for x in range(0, w, 40):
self.canvas.create_line(x, 0, x, h, fill="#f0f0f0")
for y in range(0, h, 40):
self.canvas.create_line(0, y, w, y, fill="#f0f0f0")
# 파동선 그리기
points = self.calculate_wave_points(self.amplitude, self.frequency, self.instrument, w, h)
for i in range(len(points) - 1):
self.canvas.create_line(points[i][0], points[i][1], points[i+1][0], points[i+1][1], fill="#007BFF", width=3)
# 상태 텍스트 표시
info_text = f"선택된 악기: {self.instrument} | 진폭(크기): {self.amplitude} | 진동수(높낮이): {self.frequency}Hz"
self.canvas.create_text(15, h - 15, text=info_text, anchor="w", font=("맑은 고딕", 10, "bold"), fill="#333333")
def draw_compare_canvas(self):
# 2. 중첩 캔버스 그리기
self.compare_canvas.delete("all")
w, h = 320, 200
self.compare_canvas.create_line(0, h/2, w, h/2, fill="#555555", dash=(2, 2))
colors = ["#FF5722", "#4CAF50", "#E91E63"] # 캡처 파동 색상 (주황, 초록, 핑크)
for idx, wave in enumerate(self.captured_waves):
pts = self.calculate_wave_points(wave['amp'], wave['freq'], wave['inst'], w, h)
for i in range(len(pts) - 1):
self.compare_canvas.create_line(pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1], fill=colors[idx], width=2)
# 이벤트 함수들
def on_amp_change(self, val):
self.amplitude = int(val)
self.draw_wave()
def on_freq_change(self, val):
self.frequency = float(val)
self.draw_wave()
def on_inst_change(self, event):
self.instrument = self.inst_combo.get()
self.draw_wave()
def capture_wave(self):
if len(self.captured_waves) >= 3:
self.captured_waves.pop(0) # 3개 넘으면 제일 오래된 것 삭제
self.history_box.delete(0)
# 현재 상태 저장
wave_data = {
'inst': self.instrument,
'amp': self.amplitude,
'freq': self.frequency
}
self.captured_waves.append(wave_data)
# 리스트박스에 표시
colors_text = ["🔴", "🟢", "🔵"]
idx = len(self.captured_waves) - 1
display_text = f"{colors_text[idx]} [{wave_data['inst'].split()[0]}] 진폭:{wave_data['amp']} / 진동수:{wave_data['freq']}"
self.history_box.insert(tk.END, display_text)
# 중첩 그래프 업데이트
self.draw_compare_canvas()
def reset_captures(self):
self.captured_waves = []
self.history_box.delete(0, tk.END)
self.compare_canvas.delete("all")
if __name__ == "__main__":
root = tk.Tk()
app = SoundWaveApp(root)
root.mainloop()