ট্র্যাক
/
x86-64 Assembly
x86-64 Assembly
/
অনুশীলনী
/
মিক্সিং কনসোল
মিক্সিং কনসোল

মিক্সিং কনসোল

লার্নিং অনুশীলনী

ভূমিকা

SIMD: ইন্টিজার

আগের একটি কনসেপ্টে প্যাকড ফ্লোটিং-পয়েন্ট ভ্যালু নিয়ে আলোচনা করা হয়েছিল: একটি xmm রেজিস্টারে একাধিক সংখ্যা রাখা হয়, আর সেগুলোর উপর লেনভিত্তিক ও সমান্তরালে কাজ করা হয়। একই xmm রেজিস্টারগুলোতে প্যাকড ইন্টিজার-ও রাখা যায়।

সিনট্যাক্স

SIMD ফ্লোটিং-পয়েন্ট মডেলের বেশিরভাগটাই প্যাকড ইন্টিজারের ক্ষেত্রে অপরিবর্তিতভাবে প্রযোজ্য:

  • একটি ১২৮-বিট রেজিস্টারকে লেনে ভাগ করা হয়
  • ইনস্ট্রাকশনগুলো একই অবস্থানের লেনগুলোর উপর সমান্তরালে কাজ করে
  • মেমোরি অপারেন্ড একই ১৬-বাইট অ্যালাইনমেন্ট নিয়ম মেনে চলে

তবে সিনট্যাক্স একটু আলাদা:

  1. একটি প্রিফিক্স p, যা বোঝায় ইনস্ট্রাকশনটি প্যাকড ডেটার উপর কাজ করে।
  2. যে অপারেশনটি করা হচ্ছে, যার নাম নন-SIMD (যাকে স্কেলার-ও বলা হয়) সমতুল্যের মতোই (যেমন, add, mul ইত্যাদি)।
  3. একটি সাফিক্স, যা প্রতিটি লেনের সাইজ বোঝায়।

প্যাকড ইন্টিজারের চারটি লেন সাইজ আছে, আর প্রতিটির নিজস্ব সাফিক্স আছে:

লেনের প্রস্থ বাইট ১২৮ বিটে লেনের সংখ্যা সাফিক্স
বাইট ১ ১৬ b
ওয়ার্ড ২ ৮ w
ডিওয়ার্ড ৪ ৪ d
কিউওয়ার্ড ৮ ২ q

যেমন:

ইনস্ট্রাকশন অর্থ
paddb প্যাকড add, ৮-বিট লেন (১৬টি লেন)
paddw প্যাকড add, ১৬-বিট লেন (৮টি লেন)
paddd প্যাকড add, ৩২-বিট লেন (৪টি লেন)
paddq প্যাকড add, ৬৪-বিট লেন (২টি লেন)

কিছু ইনস্ট্রাকশন ইনপুট হিসেবে এক সাইজের লেন নেয়, কিন্তু আউটপুট দেয় অন্য সাইজের। তারা একই সাধারণ কনভেনশন অনুসরণ করে, তবে দুটি সাইজ সাফিক্স নিয়ে। প্রথমটি ইনপুট লেনের সাইজ বোঝায়, দ্বিতীয়টি আউটপুট লেনের সাইজ:

ইনস্ট্রাকশন অর্থ
pmovsxwd প্যাকড movsx, ১৬-বিট লেন থেকে ৩২-বিট লেনে
pmuldq প্যাকড mul, ৩২-বিট লেন থেকে ৬৪-বিট লেনে

মেমোরি মুভ

ইন্টিজার নামের দুটি ইনস্ট্রাকশন একটি xmm রেজিস্টার ও মেমোরির মধ্যে ১২৮ বিট কপি করে:

ইনস্ট্রাকশন বিবরণ
movdqa একটি অ্যালাইনড লোকেশনে বা সেখান থেকে প্যাকড ইন্টিজার কপি করে
movdqu একটি আন-অ্যালাইনড লোকেশনে বা সেখান থেকে প্যাকড ইন্টিজার কপি করে

এগুলো movaps ও movups-এর মতোই আচরণ করে: movdqa মিসঅ্যালাইনড অ্যাড্রেসে ফল্ট করে, অন্যদিকে movdqu যেকোনো অ্যাড্রেস গ্রহণ করে। চারটিই ১২৮ বিট কপি করে, সেগুলোকে ব্যাখ্যা না করেই। ইন্টিজার নামের জোড়াটি কনভেনশন অনুযায়ী ইন্টিজার ডেটার সাথে ব্যবহৃত হয়, বাধ্যবাধকতা হিসেবে নয়।

Note

এখানে dq মানে double-qword, অর্থাৎ ১২৮ বিট (১৬ বাইট)।

যোগ/বিয়োগ

যোগ ও বিয়োগ নামকরণের নিয়মটি অনুসরণ করে:

paddb xmm0, xmm1 ; 16 lanes: each  8-bit, xmm0 += xmm1
paddw xmm2, xmm3 ;  8 lanes: each 16-bit, xmm2 += xmm3

psubd xmm4, xmm5 ;  4 lanes: each 32-bit, xmm4 -= xmm5
psubq xmm6, xmm7 ;  2 lanes: each 64-bit, xmm6 -= xmm7

সাইনড ও আনসাইনডের জন্য আলাদা কোনো রূপ নেই। টু'স কমপ্লিমেন্টে যোগ ও বিয়োগ একই বিট উৎপন্ন করে, লেনগুলোকে সাইনড হিসেবে পড়া হোক বা আনসাইনড হিসেবে, তাই একটি ইনস্ট্রাকশনই দুটোর কাজ করে। ব্যাখ্যাটা আপনার হাতে, ঠিক স্কেলার add ও sub-এর মতোই।

এই ইনস্ট্রাকশনগুলো ওভারফ্লো হলে র্যাপ অ্যারাউন্ড করে, তাদের স্কেলার সমতুল্যগুলোর মতোই। একটি ৮-বিট লেন ২৫৬-এর মডুলোতে মান ধারণ করে, তাই 200 + 100-এর উপর paddb চালালে ফল আসে 300 - 256 = 44, যে বিটগুলো ধরে না সেগুলো বাদ দিয়ে।

মনে রাখবেন, ওই অতিরিক্ত বিটগুলো পরের লেনে ক্যারি হয়ে যায় না। একই রেজিস্টার ভাগাভাগি করা সত্ত্বেও প্রতিটি লেনের উপর অন্যগুলো থেকে আলাদাভাবে কাজ করা হয়।

স্যাচুরেটিং যোগ/বিয়োগ

ইন্টিজার SIMD এমন একটি অপারেশন যোগ করে যা ফ্লোটিং-পয়েন্ট SIMD-তে নেই: স্যাচুরেটিং যোগ ও বিয়োগ, যা র্যাপ করার বদলে ক্ল্যাম্প করে। লেনের রেঞ্জের উপরের কোনো ফল লেনটি যে সর্বোচ্চ মান ধারণ করতে পারে সেটিই হয়ে যায়; রেঞ্জের নিচের ফল হয়ে যায় সর্বনিম্ন মান।

স্যাচুরেটিং রূপগুলো সাইজ সাফিক্সের আগে s (সাইনড) বা us (আনসাইনড) বসায়:

ইনস্ট্রাকশন অর্থ
paddsb add, স্যাচুরেটিং, সাইনড ৮-বিট লেন
paddusb add, স্যাচুরেটিং, আনসাইনড ৮-বিট লেন
psubsw sub, স্যাচুরেটিং, সাইনড ১৬-বিট লেন
psubusw sub, স্যাচুরেটিং, আনসাইনড ১৬-বিট লেন

ক্ল্যাম্পের রেঞ্জ হলো সংশ্লিষ্ট সাইজ ও সাইনডনেসের একটি ইন্টিজারের সম্পূর্ণ উপস্থাপনযোগ্য রেঞ্জ। একটি বাইটের ক্ষেত্রে:

  • আনসাইনড বাইট [0, 255]-এ ক্ল্যাম্প হয়: 200 + 100-এর উপর paddusb দেয় 255, আর 5 - 10-এর উপর psubusb দেয় 0।
  • সাইনড বাইট [-128, 127]-এ ক্ল্যাম্প হয়: 100 + 50-এর উপর paddsb দেয় 127।

লেনে যখন সীমাবদ্ধ কোনো রাশ থাকে, যেমন পিক্সেলের একটি চ্যানেল বা অডিও স্যাম্পল, তখন স্যাচুরেশন গুরুত্বপূর্ণ হয়ে ওঠে। র্যাপ অ্যারাউন্ড করলে বেশি উজ্জ্বল একটি পিক্সেল অন্ধকার হয়ে যেত, অন্যদিকে ক্ল্যাম্পিং সেটিকে সর্বোচ্চ উজ্জ্বলতায় ধরে রাখে, আর সেটিই আপনার কাঙ্ক্ষিত ফল।

Note

স্যাচুরেটিং যোগ ও বিয়োগ কেবল বাইট ও ওয়ার্ড লেনের জন্যই আছে, ডিওয়ার্ড বা কিউওয়ার্ডের জন্য নয়।

গুণ

দুটি N-বিট মান গুণ করলে 2N-বিট গুণফল আসতে পারে, কিন্তু গন্তব্য লেনটি কেবল N বিট চওড়া। SIMD অপারেশনগুলো গুণফলের কোন অর্ধেকটি রাখতে হবে তা উল্লেখ করে এটি সমাধান করে, হয় নিচের N বিট, নয় উপরের N বিট।

১৬-বিট লেনের জন্য তিনটি ইনস্ট্রাকশন ব্যবহৃত হয়:

ইনস্ট্রাকশন অর্থ
pmullw mul, ১৬-বিট লেন, প্রতিটি গুণফলের নিচের ১৬ বিট রাখে
pmulhw mul, ১৬-বিট লেন, উপরের ১৬ বিট রাখে, সাইনড অপারেন্ড
pmulhuw mul, ১৬-বিট লেন, উপরের ১৬ বিট রাখে, আনসাইনড অপারেন্ড

অপারেন্ডগুলোকে সাইনড বা আনসাইনড হিসেবে পড়া হোক, গুণফলের নিচের ১৬ বিট একই থাকে, তাই pmullw কেবল একটি।

কিন্তু উপরের ১৬ বিট ফলাফলের সাইনডনেস অনুযায়ী ভিন্ন হয়। এ কারণেই হাই-হাফ গুণের জন্য সাইনড ও আনসাইনড গুণের আলাদা রূপ রয়েছে:

  1. pmulhw, সাইনড গুণের জন্য।
  2. pmulhuw, একটি বাড়তি u সহ, আনসাইনড গুণের জন্য।

সিনট্যাক্সটি খেয়াল করুন:

  1. একটি p, প্যাকড ইন্টিজারের জন্য।
  2. যে অপারেশনটি করা হচ্ছে, mul।
  3. একটি h, যা বোঝায় ফলাফলের উপরের ("হাই") বিটগুলো বেছে নেওয়া হচ্ছে।
  4. ঐচ্ছিক একটি u, যদি ফলাফলটিকে আনসাইনড হিসেবে ব্যাখ্যা করতে হয় (অর্থাৎ, এতে সাইন এক্সটেনশন করা হয় না)।
  5. সবশেষে সাইজ সাফিক্স w, যা বোঝায় এটি একটি ওয়ার্ড (১৬-বিট) অপারেশন।

ওয়ার্ডের জন্য প্যাকড গুণ উপরের নিয়মটি হুবহু অনুসরণ করে। ডিওয়ার্ড (৩২-বিট) গুণও নিয়মটি অনুসরণ করে, তবে নিচের অর্ধেক বেছে নেওয়া ভ্যারিয়েন্টটিই কেবল আছে: pmulld।

উপরের বিট বেছে নেওয়ার মতো কোনো ভ্যারিয়েন্ট ডিওয়ার্ড গুণের জন্য নেই। তবে এমন ভ্যারিয়েন্ট আছে যা গুণটিকে প্রশস্ত করে, জোড়-সূচক লেনের (অর্থাৎ ০ ও ২ নম্বর অবস্থানের লেনগুলোর) সম্পূর্ণ ৬৪-বিট গুণফল সংরক্ষণ করে:

ইনস্ট্রাকশন অর্থ
pmuludq জোড়-সূচক ৩২-বিট এলিমেন্টগুলোকে আনসাইনড হিসেবে গুণ করে ২টি সম্পূর্ণ ৬৪-বিট গুণফল তৈরি করে
pmuldq জোড়-সূচক ৩২-বিট এলিমেন্টগুলোকে সাইনড হিসেবে গুণ করে ২টি সম্পূর্ণ ৬৪-বিট গুণফল তৈরি করে

খেয়াল করুন, সিনট্যাক্সে dq ব্যবহৃত হয়, আনসাইনড গুণের ক্ষেত্রে সম্ভবত তার আগে একটি u থাকে। কারণ এই ইনস্ট্রাকশনগুলো dword লেন ইনপুট নেয় আর qword লেন আউটপুট দেয়।

ভাগ

প্যাকড ইন্টিজার ভাগ বলে কিছু নেই। যেসব কোডে এটি দরকার, সেগুলো মানগুলোকে ফ্লোটিং-পয়েন্টে রূপান্তর করে, ভাগ করে, তারপর আবার ফিরিয়ে রূপান্তর করে।

ইন্টিজার প্রশস্ত করা

movsx ও movzx-এর প্যাকড সমতুল্য রূপ আছে। ইনপুটের লেন সাইজ আর আউটপুটের লেন সাইজ আলাদা হয় এমন ইনস্ট্রাকশনের জন্য আমরা যে সিনট্যাক্সের কথা বলেছি, এগুলোও সেটিই অনুসরণ করে:

pmovsxwd xmm0, xmm1   ; 4 words -> 4 dwords, sign-extended
pmovzxbw xmm0, xmm1   ; 8 bytes -> 8 words, zero-extended

মনে রাখবেন, লেনের সংখ্যা নির্ধারিত হয় বড় (আউটপুট) প্রস্থ দিয়ে। ইনস্ট্রাকশনটি সোর্সের নিচের অংশ থেকে ঠিক ততগুলো লেন পড়ে। সিঙ্গেল ও ডাবল-প্রিসিশন ফ্লোটের মধ্যে প্যাকড cvt ইনস্ট্রাকশনের ক্ষেত্রে আমরা এই একই সেমান্টিকস আগেই দেখেছি।

ইন্টিজার ও ফ্লোটের মধ্যে রূপান্তর

৩২-বিট সাইনড ইন্টিজার ও ফ্লোটিং-পয়েন্ট লেনের মধ্যে রূপান্তর করার ইনস্ট্রাকশন আছে। এগুলো cvt ও cvtt ইনস্ট্রাকশনের স্বাভাবিক সিনট্যাক্স অনুসরণ করে, তবে si-এর বদলে প্যাকড ইন্টিজার বোঝানো হয় dq দিয়ে:

cvtdq2ps xmm0, xmm1 ; convert 32-bit signed integers in xmm1 to 32-bit floats in xmm0
cvtdq2pd xmm2, xmm3 ; convert 32-bit signed integers in xmm3 to 64-bit floats in xmm2
cvtps2dq xmm4, xmm5 ; convert 32-bit floats in xmm5 to 32-bit signed integers in xmm4
Caution

যেসব ইনস্ট্রাকশন প্যাকড ইন্টিজার বোঝাতে pi ব্যবহার করে, সেগুলো লিগ্যাসি mmx রেজিস্টারে লেখে এবং x86-64-এ কার্যত ডেপ্রিকেটেড। dq রূপগুলোকে প্রাধান্য দিন, যেগুলো xmm রেজিস্টার ব্যবহার করে।

ফ্লোটিং-পয়েন্ট সংখ্যা ও ইন্টিজারের মধ্যে স্কেলার রূপান্তরের জন্য যে কথাগুলো বলা হয়েছিল, সেগুলো এখানেও প্রযোজ্য। MXCSR নামের একটি বিশেষ রেজিস্টার অনুযায়ী ফ্লোটিং-পয়েন্ট সংখ্যাগুলো রাউন্ড করা হয়, আর ফাংশনে ঢোকার সময় তার মোড কী থাকবে তা আপনি ধরে নিতে পারেন না। একটি cvtt ভ্যারিয়েন্ট আছে (বাড়তি একটি t সহ) যা সবসময় ফলাফলটি ট্রাঙ্কেট করে।

রূপান্তরের আগে প্যাকড ফ্লোটিং-পয়েন্ট মানগুলোকে জানা একটি অবস্থায় আনতে round ব্যবহার করাও সম্ভব। রাউন্ড কন্ট্রোল মানটি স্কেলার round-এর মতোই, আর প্যাকড ফ্লোটিং-পয়েন্ট মানের ক্ষেত্রেও সিনট্যাক্স একই:

roundps xmm0, xmm1, 1 ; xmm0 = floor(xmm1), packed 32-bit floats
roundpd xmm2, xmm3, 2 ; xmm2 = ceil(xmm3), packed 64-bit floats

নির্দেশনা

আপনি একটি রেকর্ডিং স্টুডিওর ছোট একটি মিক্সিং কনসোলের ডিজিটাল কোর লিখছেন।

শব্দ স্যাম্পলের একটি স্ট্রিম হিসেবে আসে। প্রতিটি স্যাম্পল হলো একটি সংখ্যা, যা একটি মুহূর্তে লাউডস্পিকারের কোনের অবস্থান বর্ণনা করে। একটি কনসোল একবারে স্যাম্পলের পুরো একটি ব্লক প্রসেস করে এবং প্রতিটির উপর একই অপারেশন প্রয়োগ করে।

আপনার পাঁচটি কাজ আছে, যার প্রতিটি মেমরিতে রাখা স্যাম্পলের একটি ব্লকের উপর কাজ করে। আপনি অপারেন্ডগুলো মেমরি অ্যাড্রেসের মাধ্যমে পেয়ে থাকেন এবং নিজের উত্তর একটি রেজাল্ট অ্যাড্রেসের মাধ্যমে লেখেন।

Note

এই অনুশীলনীর গণনাগুলো স্কেলার অপারেশনে নয়, বরং SIMD ইনস্ট্রাকশন ব্যবহার করে করা উচিত।

1. দুটি ট্র্যাক মিক্স করা

স্টুডিও মাঝে মাঝে বিভিন্ন ট্র্যাকে বাদ্যযন্ত্র রেকর্ড করে। সেগুলো একই স্পিকার দিয়ে বাজানোর জন্য আপনি দুটি ট্র্যাক যোগ করে একটিতে পরিণত করেন।

mix_tracks ফাংশনটি ইমপ্লিমেন্ট করুন, যা দুটি ট্র্যাক স্যাম্পল ধরে ধরে যোগ করে। প্রতিটি ট্র্যাক ৮টি সাইনড ১৬-বিট স্যাম্পলের একটি ব্লক। সাইনড ১৬-বিট রেঞ্জ ছাড়িয়ে যাওয়া একটি যোগফল র্যাপ হওয়ার বদলে ক্ল্যাম্প হতে হবে।

এই ফাংশনটি আর্গুমেন্ট হিসেবে নেয়, এই ক্রমে:

  • result: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, এমন একটি বাফারের জন্য যেখানে ৮টি মিক্স করা স্যাম্পল লেখা হয়।
  • track_a: প্রথম ট্র্যাকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ৮টি সাইনড ১৬-বিট স্যাম্পল থাকে।
  • track_b: দ্বিতীয় ট্র্যাকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ৮টি সাইনড ১৬-বিট স্যাম্পল থাকে।
track_a = {10000, 30000, 20000, -30000, -20000, -100,  100, 0}
track_b = { 5000,  5000, 20000,  -5000, -20000,  100, -100, 0}
result  = {15000, 32767, 32767, -32768, -32768,    0,    0, 0}

এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই।

2. ব্লিড সরানো

যখন দুটি মাইক্রোফোন একসাথে রেকর্ড করে, তখন প্রত্যেকটিই অন্যটির সোর্সের একটি ক্ষীণ কপি ধরে ফেলে। এই অবাঞ্ছিত কপিটিকেই ব্লিড বলা হয়। এই ব্লিড একবার মাপা হয়ে গেলে, ট্র্যাকটি পরিষ্কার করতে সেটি আবার বিয়োগ করে বাদ দিতে হয়।

remove_bleed ফাংশনটি ইমপ্লিমেন্ট করুন, যা ট্র্যাক ব্লক থেকে ব্লিড ব্লক স্যাম্পল ধরে ধরে বিয়োগ করে। প্রতিটি ব্লক ৮টি সাইনড ১৬-বিট স্যাম্পল, এবং বিয়োগফলটি স্যাচুরেট করতে হবে।

এই ফাংশনটি আর্গুমেন্ট হিসেবে নেয়, এই ক্রমে:

  • result: মেমরি অ্যাড্রেস, যেখানে ৮টি পরিষ্কার করা স্যাম্পল লেখা হয় (অ্যালাইনমেন্ট নিশ্চিত নয়)।
  • track: রেকর্ড করা ট্র্যাকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, ৮টি সাইনড ১৬-বিট স্যাম্পল।
  • bleed: বাদ দিতে হবে এমন ব্লিডের মেমরি অ্যাড্রেস, ৮টি সাইনড ১৬-বিট স্যাম্পল (অ্যালাইনমেন্ট নিশ্চিত নয়)।
track  = {10000, -30000, 5000, 32767, 0,  200, -200, 15000}
bleed  = { 2000,   5000, 5000,    -5, 0,  300,  300,  5000}
result = { 8000, -32768,    0, 32767, 0, -100, -500, 10000}

এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই।

3. মিটার একত্র করা

প্রতিটি চ্যানেলের উপরে থাকে একটি লেভেল মিটার, একটি ছোট বার যার উজ্জ্বলতা বোঝায় সেই চ্যানেলটি কতটা জোরে। সামনের প্যানেলে একটি মাস্টার মিটার আছে, যা দুটি চ্যানেল একসাথে যতটা উজ্জ্বল হয় ঠিক ততটা উজ্জ্বলভাবে জ্বলজ্বল করা উচিত। এটিকে চালাতে আপনি দুটি মিটার ব্লক যোগ করেন।

লক্ষ্য করুন, উজ্জ্বল একটি মিটার আবার অন্ধকারে ফিরে না গিয়ে পূর্ণ উজ্জ্বলতাতেই থাকে, তাই যোগফলটি স্যাচুরেটিং।

combine_meters ফাংশনটি ইমপ্লিমেন্ট করুন, যা দুটি মিটার ব্লক স্যাম্পল ধরে ধরে যোগ করে। এই ফাংশনটি আর্গুমেন্ট হিসেবে নেয়, এই ক্রমে:

  • result: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ১৬টি একত্র করা ভ্যালু লেখা হয়।
  • meter_a: প্রথম চ্যানেলের মিটার ব্লকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ১৬টি প্যাকড আনসাইনড ৮-বিট ভ্যালু থাকে।
  • meter_b: দ্বিতীয় চ্যানেলের মিটার ব্লকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ১৬টি প্যাকড আনসাইনড ৮-বিট ভ্যালু থাকে।

প্রতিটি ব্লক ১৬টি আনসাইনড ৮-বিট ভ্যালু। এগুলো যোগ করতে হবে, আনসাইনড রেঞ্জে স্যাচুরেট করে।

meter_a = {200, 100,  50, 255, 0, 128, 10, 240, 1, 2, 3, 4, 5, 6, 7, 8}
meter_b = {100, 100, 200,   1, 0, 128, 10,  20, 1, 2, 3, 4, 5, 6, 7, 8}
result  = {255, 200, 250, 255, 0, 255, 20, 255, 2, 4, 6, 8, 10, 12, 14, 16}

এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই।

4. ফেড প্রয়োগ করা

গানটি যেন হঠাৎ থেমে না যায়, বরং শেষে ফেড আউট হয়। আপনার কাছে গেইন সহগ-এর একটি ব্লক আছে, প্রতি স্যাম্পলে একটি, যা ক্রমশ কম জোরে থেকে আরও কম জোরে নামতে থাকে।

প্রতিটি সহগ একটি ভগ্নাংশ, যা 65536-এর সাপেক্ষে প্রকাশ করা হয়। উদাহরণস্বরূপ, কোনো স্যাম্পলের মান 16384 হলে, এটি আসলে 16384 / 65536 প্রকাশ করে, যা 1 / 4। ব্যবহারযোগ্য বৃহত্তম মান 32767, যা অর্ধেকের একটু কম।

যেহেতু 65536 হলো 2^16, তাই একটি স্যাম্পলকে তার সহগ দিয়ে গুণ করে গুণফলের উচ্চ অর্ধেক রাখলে sample * gain / 65536 পাওয়া যায়।

apply_fade ফাংশনটি ইমপ্লিমেন্ট করুন, যা প্রতিটি স্যাম্পলকে তার সহগ দিয়ে স্কেল করে। এই ফাংশনটি আর্গুমেন্ট হিসেবে নেয়, এই ক্রমে:

  • result: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে স্কেল করা সব স্যাম্পল লেখা হয়।
  • track: ট্র্যাকের ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে n সংখ্যক সাইনড ১৬-বিট স্যাম্পল থাকে।
  • gains: গেইন সহগগুলোর ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে n সংখ্যক সাইনড ১৬-বিট ভ্যালু থাকে, প্রতিটি 0 থেকে 32767 রেঞ্জে।
  • n: স্যাম্পলের সংখ্যা, যা সর্বদা অশূন্য এবং 8-এর গুণিতক, একটি ৬৪-বিট আনসাইনড ইন্টিজার হিসেবে।

লক্ষ্য করুন, ট্র্যাকটি একটি অ্যারে, যেখানে n সংখ্যক সাইনড ১৬-বিট স্যাম্পল থাকে, এবং n কখনোই শূন্য নয়, সর্বদা 8-এর গুণিতক। প্রতিটি স্যাম্পলের জন্য স্কেল করা ফলাফল হলো সাইনড গুণফলের উচ্চ ১৬ বিট: sample * gain।

track  = {20000, 20000, 20000, 20000, -20000, -20000, -20000, -20000}
gains  = {32767, 24576, 16384,  8192,  32767,  24576,  16384,   8192}
result = { 9999,  7500,  5000,  2500, -10000,  -7500,  -5000,  -2500}
Note

উচ্চ অর্ধেক রাখলে তা ঋণাত্মক অসীমের দিকে রাউন্ড করে। এ কারণেই 20000 * 32767 স্কেল হয়ে 9999 হয়, আবার -20000 * 32767 স্কেল হয়ে -9999 নয়, বরং -10000 হয়।

এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই।

5. একটি ট্র্যাক অ্যাটেনুয়েট করা

ড্রামারকে অনেক বেশি জোরে রেকর্ড করা হয়েছিল। পুরো ট্র্যাকটি নামিয়ে আনতে আপনি প্রতিটি স্যাম্পলকে একটি ধ্রুবক দিয়ে ভাগ করেন।

attenuate_track ফাংশনটি ইমপ্লিমেন্ট করুন। এই ফাংশনটি আর্গুমেন্ট হিসেবে নেয়, এই ক্রমে:

  • result: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ফলাফলগুলো ৩২-বিট সাইনড ইন্টিজার হিসেবে লেখা হয়।
  • samples: স্যাম্পলগুলোর ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, n সংখ্যক সাইনড ১৬-বিট ভ্যালু।
  • divisor: ভাজকের ৪টি অভিন্ন কপির ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, ৩২-বিট ইন্টিজার হিসেবে।
  • n হলো স্যাম্পলের সংখ্যা, যা সর্বদা অশূন্য এবং 4-এর গুণিতক, একটি ৬৪-বিট আনসাইনড ইন্টিজার হিসেবে।

লক্ষ্য করুন, samples হলো একটি অ্যারে, যেখানে n সংখ্যক সাইনড ১৬-বিট স্যাম্পল থাকে, এবং n কখনোই শূন্য নয়, সর্বদা 4-এর গুণিতক।

ভাজকটি একই ৩২-বিট ইন্টিজারের 4টি কপি হিসেবে দেওয়া হয়, যা আগেই প্রতিলিপি করা থাকে যাতে একবারে চারটি লেন ভরাতে পারে।

ইন্টিজার SIMD-তে প্যাকড ভাগ নেই, তাই কাজটি প্যাকড ফ্লোটিং পয়েন্টে করা হয়, তারপর result-এ রাখার জন্য আবার প্যাকড ৩২-বিট ইন্টিজারে কনভার্ট করা হয়। শূন্যের দিকে ট্রাংকেট করে আবার কনভার্ট করুন, ফলে 2.3-এর ভাগফল 2 হয় আর -2.3-এর ভাগফল -2 হয়।

samples = {300, 150, 90, -120, 100, 50, -50, 7}
divisor = {3, 3, 3, 3}
result  = {100,  50, 30,  -40,  33, 16, -16, 2}

এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই।

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
x86-64 Assembly Exercism

মিক্সিং কনসোল শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।