अपने Flutter गेम में साउंड और संगीत जोड़ें

1. शुरू करने से पहले

गेम, ऑडियोविज़ुअल अनुभव होते हैं. Flutter एक शानदार टूल है. इसकी मदद से शानदार विज़ुअल और शानदार यूआई बनाएं, ताकि आप चीज़ों को विज़ुअल के रूप में बेहतर तरीके से समझ पाएं. बची हुई सामग्री में ऑडियो है. इस कोडलैब में, अपने प्रोजेक्ट में वीडियो स्ट्रीम होने और उसके दिखने के समय का अंतर कम करने के बारे में जानने के लिए, flutter_soloud प्लगिन को इस्तेमाल करने का तरीका बताया गया है. आप एक सामान्य मचान से शुरुआत करें, ताकि आप सीधे दिलचस्प हिस्सों पर पहुंच सकें.

हेडफ़ोन का हाथ से बनाया गया इलस्ट्रेशन.

आपने यहां जो कुछ भी सीखा है उसका इस्तेमाल, सिर्फ़ गेम ही नहीं, बल्कि ऐप्लिकेशन में ऑडियो जोड़ने के लिए भी किया जा सकता है. हालांकि, करीब-करीब सभी गेम में आवाज़ और संगीत की ज़रूरत होती है, लेकिन ज़्यादातर ऐप्लिकेशन में आवाज़ और संगीत की ज़रूरत नहीं होती. इसलिए, यह कोडलैब गेम पर फ़ोकस करता है.

ज़रूरी शर्तें

  • Flutter के बारे में बुनियादी जानकारी.
  • Flutter ऐप्लिकेशन चलाने और उन्हें डीबग करने की जानकारी.

आपको ये सब सीखने को मिलेगा

  • एक शॉट की आवाज़ चलाने का तरीका.
  • गैपलेस म्यूज़िक लूप चलाने और उन्हें पसंद के मुताबिक बनाने का तरीका.
  • आवाज़ों को कम कैसे करें.
  • पर्यावरण पर पड़ने वाले असर को आवाज़ों पर कैसे लागू करें.
  • अपवादों से कैसे निपटें.
  • इन सभी सुविधाओं को एक ऑडियो कंट्रोलर में बदलने का तरीका.

आपको इन चीज़ों की ज़रूरत पड़ेगी

  • Flutter SDK टूल
  • आपकी पसंद का कोड एडिटर

2. सेट अप करें

  1. नीचे दी गई फ़ाइलें डाउनलोड करें. अगर आपका कनेक्शन धीमा है, तो चिंता न करें. आपको असल फ़ाइलों की ज़रूरत बाद में पड़ेगी, ताकि काम करते समय आप उन्हें डाउनलोड कर सकें.
  1. अपनी पसंद के नाम से, Flutter प्रोजेक्ट बनाएं.
  1. प्रोजेक्ट में lib/audio/audio_controller.dart फ़ाइल बनाएं.
  2. फ़ाइल में, यह कोड डालें:

lib/audio/audio_controller.dart

import 'dart:async';

import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  Future<void> initialize() async {
    // TODO
  }

  void dispose() {
    // TODO
  }

  Future<void> playSound(String assetKey) async {
    _log.warning('Not implemented yet.');
  }

  Future<void> startMusic() async {
    _log.warning('Not implemented yet.');
  }

  void fadeOutMusic() {
    _log.warning('Not implemented yet.');
  }

  void applyFilter() {
    // TODO
  }

  void removeFilter() {
    // TODO
  }
}

जैसा कि आपको दिख रहा है, यह सिर्फ़ आने वाले समय के फ़ंक्शन के लिए एक ढांचा है. हम यह सब कुछ इस कोडलैब के दौरान लागू करेंगे.

  1. इसके बाद, lib/main.dart फ़ाइल खोलें और फिर इसकी सामग्री को इस कोड से बदलें:

lib/main.dart

import 'dart:developer' as dev;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';

import 'audio/audio_controller.dart';

void main() async {
  // The `flutter_soloud` package logs everything
  // (from severe warnings to fine debug messages)
  // using the standard `package:logging`.
  // You can listen to the logs as shown below.
  Logger.root.level = kDebugMode ? Level.FINE : Level.INFO;
  Logger.root.onRecord.listen((record) {
    dev.log(
      record.message,
      time: record.time,
      level: record.level.value,
      name: record.loggerName,
      zone: record.zone,
      error: record.error,
      stackTrace: record.stackTrace,
    );
  });

  WidgetsFlutterBinding.ensureInitialized();

  final audioController = AudioController();
  await audioController.initialize();

  runApp(
    MyApp(audioController: audioController),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({required this.audioController, super.key});

  final AudioController audioController;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter SoLoud Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
        useMaterial3: true,
      ),
      home: MyHomePage(audioController: audioController),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.audioController});

  final AudioController audioController;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const _gap = SizedBox(height: 16);

  bool filterApplied = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flutter SoLoud Demo')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            OutlinedButton(
              onPressed: () {
                widget.audioController.playSound('assets/sounds/pew1.mp3');
              },
              child: const Text('Play Sound'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.startMusic();
              },
              child: const Text('Start Music'),
            ),
            _gap,
            OutlinedButton(
              onPressed: () {
                widget.audioController.fadeOutMusic();
              },
              child: const Text('Fade Out Music'),
            ),
            _gap,
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text('Apply Filter'),
                Checkbox(
                  value: filterApplied,
                  onChanged: (value) {
                    setState(() {
                      filterApplied = value!;
                    });
                    if (filterApplied) {
                      widget.audioController.applyFilter();
                    } else {
                      widget.audioController.removeFilter();
                    }
                  },
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
  1. ऑडियो फ़ाइलें डाउनलोड होने के बाद, अपने प्रोजेक्ट के रूट में assets नाम की एक डायरेक्ट्री बनाएं.
  2. assets डायरेक्ट्री में, दो सबडायरेक्ट्री बनाएं. एक का नाम music और दूसरी का नाम sounds.
  3. डाउनलोड की गई फ़ाइलों को अपने प्रोजेक्ट में ले जाएं, ताकि गाने की फ़ाइल assets/music/looped-song.ogg फ़ाइल में और प्यू की साउंड, इन फ़ाइलों में हो:
  • assets/sounds/pew1.mp3
  • assets/sounds/pew2.mp3
  • assets/sounds/pew3.mp3

आपके प्रोजेक्ट का स्ट्रक्चर अब कुछ इस तरह दिखना चाहिए:

प्रोजेक्ट का ट्री व्यू, जिसमें `android`, `ios` जैसे फ़ोल्डर, `README.md` और `analysis_options.yaml` जैसी फ़ाइलें शामिल हैं. इनमें से, हम `म्यूज़िक` और `साउंड` सबडायरेक्ट्री वाली `ऐसेट` डायरेक्ट्री, `main.dart` के साथ `lib` डायरेक्ट्री, और `audio_controller.dart` के साथ `ऑडियो` सबडायरेक्ट्री, और `pubspec.yaml` फ़ाइल को देखते हैं.  ऐरो नई डायरेक्ट्री और उन फ़ाइलों पर ले जाते हैं जिन्हें आपने अब तक छू लिया है.

अब जब फ़ाइलें वहां मौजूद हैं, तो आपको Flutter को उनके बारे में बताना होगा.

  1. pubspec.yaml फ़ाइल खोलें और फिर फ़ाइल के सबसे नीचे मौजूद flutter: सेक्शन को, नीचे दिए गए विकल्पों से बदलें:

pubspec.yaml

...

flutter:
  uses-material-design: true

  assets:
    - assets/music/
    - assets/sounds/
  1. flutter_soloud पैकेज और logging पैकेज पर डिपेंडेंसी जोड़ें.

pubspec.yaml

...

dependencies:
  flutter:
    sdk: flutter

  flutter_soloud: ^2.0.0
  logging: ^1.2.0

...
  1. प्रोजेक्ट चलाएं. फ़िलहाल, कुछ सुविधाएं काम नहीं कर रही हैं, क्योंकि आपने इन सेक्शन में यह सुविधा जोड़ी है.

10f0f751c9c47038.png

/flutter_soloud/src/filters/filters.cpp:21:24: warning: implicit conversion loses integer precision: 'decltype(__x.base() - __y.base())' (aka 'long') to 'int' [-Wshorten-64-to-32];

ये पहले से मौजूद SoLoud C++ लाइब्रेरी से लिए जाते हैं. इनसे सुविधाओं पर कोई असर नहीं पड़ता और इन्हें सुरक्षित तरीके से अनदेखा किया जा सकता है.

3. शुरू और बंद करें

ऑडियो चलाने के लिए, flutter_soloud प्लगिन का इस्तेमाल करें. यह प्लगिन, SoLoud प्रोजेक्ट पर आधारित है. यह Nintendo SNES Classic के गेम के साथ-साथ, अन्य गेम के लिए C++ ऑडियो इंजन है.

7ce23849b6d0d09a.png

SoLoud ऑडियो इंजन शुरू करने के लिए, यह तरीका अपनाएं:

  1. audio_controller.dart फ़ाइल में, flutter_soloud पैकेज को इंपोर्ट करें और क्लास में निजी _soloud फ़ील्ड जोड़ें.

lib/audio/audio_controller.dart

import 'dart:ui';

import 'package:flutter_soloud/flutter_soloud.dart';  // ← Add this...
import 'package:logging/logging.dart';

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;                                    // ← ... and this.

  Future<void> initialize() async {
    // TODO
  }

  ...

ऑडियो कंट्रोलर, इस फ़ील्ड की मदद से मौजूद SoLoud इंजन को मैनेज करता है. साथ ही, सभी कॉल उस पर फ़ॉरवर्ड करेगा.

  1. initialize() तरीके में, यह कोड डालें:

lib/audio/audio_controller.dart

...

  Future<void> initialize() async {
    _soloud = SoLoud.instance;
    await _soloud!.init();
  }

...

यह _soloud फ़ील्ड को भरता है और शुरू होने का इंतज़ार करता है. निम्न पर ध्यान दें:

  • SoLoud एक सिंगलटन instance फ़ील्ड उपलब्ध कराता है. कई SoLoud इंस्टेंस को इंस्टैंशिएट करने का कोई तरीका नहीं है. C++ इंजन ऐसा करने की अनुमति नहीं देता. इसलिए, Dart प्लगिन भी इसकी अनुमति नहीं देता.
  • प्लगिन की प्रोसेस एसिंक्रोनस है. इसे तब तक पूरा नहीं किया जा सकता, जब तक init() तरीका वापस नहीं आता.
  • इस उदाहरण को छोटा रखने के लिए, आपको try/catch ब्लॉक में गड़बड़ियां नहीं मिल रही हैं. आपको प्रोडक्शन कोड में ऐसा करना होगा और किसी भी गड़बड़ी की शिकायत उपयोगकर्ता से करनी होगी.
  1. dispose() तरीके में, यह कोड डालें:

lib/audio/audio_controller.dart

...

  void dispose() {
    _soloud?.deinit();
  }

...

ऐप्लिकेशन से बाहर निकलते समय, SoLoud को बंद करना अच्छी बात है. हालांकि, ऐसा करना अच्छा नहीं रहेगा और भले ही आप ऐसा न करें.

  1. ध्यान दें कि AudioController.initialize() तरीके को पहले ही main() फ़ंक्शन से कॉल किया जा चुका है. इसका मतलब है कि हॉट-रीस्टार्ट करने से प्रोजेक्ट, बैकग्राउंड में SoLoud शुरू हो जाता है. हालांकि, बैकग्राउंड में कुछ आवाज़ें चलाने से पहले ही, आपका कोई फ़ायदा नहीं होता.

4. एक शॉट में साउंड चलाएं

कोई ऐसेट लोड करें और उसे चलाएं

अब आपको पता है कि ब्राउज़र के शुरू होने पर, SoLoud को शुरू किया जा चुका है. इसलिए, इसे साउंड चलाने के लिए कहा जा सकता है.

SoLoud, ऑडियो सोर्स के बीच अंतर करता है. ऑडियो सोर्स में किसी आवाज़ के बारे में बताने के लिए इस्तेमाल किया गया डेटा और मेटाडेटा होता है. साथ ही, वह "साउंड इंस्टेंस" के बीच भी अंतर करता है. इन साउंड इंस्टेंस, असल में चलाई जाने वाली आवाज़ें होती हैं. ऑडियो सोर्स का एक उदाहरण यह हो सकता है कि कोई mp3 फ़ाइल मेमोरी में लोड की गई हो, जिसे चलाया जा सके, और जिसे AudioSource क्लास के इंस्टेंस के ज़रिए दिखाया जाता हो. जब भी इस ऑडियो सोर्स को चलाया जाता है, SoLoud एक "साउंड इंस्टेंस" बनाता है जिसे SoundHandle टाइप से दिखाया जाता है.

इसे लोड करने पर आपको एक AudioSource इंस्टेंस मिलता है. उदाहरण के लिए, अगर आपकी एसेट में कोई mp3 फ़ाइल है, तो AudioSource पाने के लिए उसे लोड करें. इसके बाद, आप SoLoud को यह AudioSource चलाने के लिए कहते हैं. इसे एक साथ कई बार चलाया जा सकता है.

किसी ऑडियो सोर्स का इस्तेमाल करने के बाद, उसे SoLoud.disposeSource() तरीके से नष्ट किया जाता है.

कोई ऐसेट लोड करने और उसे चलाने के लिए, यह तरीका अपनाएं:

  1. AudioController क्लास की playSound() तरीके में, यह कोड डालें:

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    final source = await _soloud!.loadAsset(assetKey);
    await _soloud!.play(source);
  }

  ...
  1. फ़ाइल सेव करें, फिर से लोड करें, और फिर घंटी बजाएं को चुनें. आपको एक अजीब प्यू की आवाज़ सुनाई देगी. निम्न पर ध्यान दें:
  • दिया गया assetKey आर्ग्युमेंट, assets/sounds/pew1.mp3 जैसा कुछ है. यह वही स्ट्रिंग है जो किसी अन्य ऐसेट-लोडिंग Flutter API (एपीआई) को उपलब्ध कराई जाती है, जैसे कि Image.asset() विजेट.
  • SoLoud इंस्टेंस, loadAsset() तरीके का इस्तेमाल करता है. यह Flutter प्रोजेक्ट की ऐसेट से एक ऑडियो फ़ाइल एसिंक्रोनस रूप से लोड करता है और AudioSource क्लास का इंस्टेंस दिखाता है. फ़ाइल सिस्टम (loadFile() तरीका) से फ़ाइल लोड करने और यूआरएल से नेटवर्क पर लोड करने के लिए एक जैसे तरीके हैं (loadUrl() तरीका).
  • इसके बाद, हासिल किए गए नए AudioSource इंस्टेंस को SoLoud के play() तरीके को भेजा जाता है. इस तरीके से, SoundHandle टाइप का इंस्टेंस मिलता है, जो चल रही नई आवाज़ को दिखाता है. इस हैंडल को SoLoud के अन्य तरीकों की मदद से, आवाज़ को रोकने, बंद करने या उसके वॉल्यूम में बदलाव करने जैसे कामों के लिए भेजा जा सकता है.
  • हालांकि, play() एसिंक्रोनस तरीका है, लेकिन वीडियो तुरंत चलना शुरू हो जाता है. flutter_soloud पैकेज, C कोड को सीधे और सिंक्रोनस रूप से कॉल करने के लिए, Dart के फ़ॉरेन फ़ंक्शन इंटरफ़ेस (FFI) का इस्तेमाल करता है. Dart कोड और प्लैटफ़ॉर्म कोड के बीच आगे-पीछे ऐसा सामान्य मैसेज नहीं मिलता है जो ज़्यादातर Flutter प्लगिन में इस्तेमाल होता है. कुछ तरीकों के एसिंक्रोनस होने का एक ही वजह है कि प्लगिन के कुछ कोड, अपने आइसोलेटेड में चलता है और Dart आइसोलेटेड के बीच कम्यूनिकेशन एसिंक्रोनस होता है.
  • आप बस दावा करते/करती हैं कि _soloud! के साथ _soloud फ़ील्ड खाली नहीं होता है. एक बार फिर, जवाब को छोटा बनाने के लिए. प्रोडक्शन कोड को उस स्थिति में सही तरीके से काम करना चाहिए जब डेवलपर, ऑडियो कंट्रोलर के पूरी तरह से शुरू होने से पहले आवाज़ चलाने की कोशिश करता है.

अपवादों के साथ डील करें

शायद आपने गौर किया हो कि आपने एक बार फिर से उन अपवादों को अनदेखा किया है. चलिए, सीखने के इस खास तरीके के लिए, समस्या को हल करते हैं. (कम शब्दों में जानकारी देने के लिए, कोडलैब इस सेक्शन के बाद अपवादों को अनदेखा कर देता है.)

  • इस मामले में अपवादों को समझने के लिए, playSound() तरीके की दो लाइनों को try/catch ब्लॉक में रैप करें और सिर्फ़ SoLoudException के इंस्टेंस कैप्चर करें.

lib/audio/audio_controller.dart

  ...

  Future<void> playSound(String assetKey) async {
    try {
      final source = await _soloud!.loadAsset(assetKey);
      await _soloud!.play(source);
    } on SoLoudException catch (e) {
      _log.severe("Cannot play sound '$assetKey'. Ignoring.", e);
    }
  }

  ...

SoLoud में कई अपवाद शामिल होते हैं, जैसे कि SoLoudNotInitializedException या SoLoudTemporaryFolderFailedException अपवाद. हर तरीके के एपीआई दस्तावेज़ में, थ्रेश किए जा सकने वाले अपवादों के बारे में बताया जाता है.

SoLoud सभी अपवादों के लिए पैरंट क्लास की सुविधा देती है, जिसमें SoLoudException अपवाद शामिल है. इससे ऑडियो इंजन के काम करने से जुड़ी सभी गड़बड़ियों की जानकारी पाने में मदद मिलती है. यह उन मामलों में खास तौर पर मददगार होता है जहां ऑडियो चलाना ज़रूरी नहीं है. उदाहरण के लिए, अगर आपको खिलाड़ी के गेम सेशन को सिर्फ़ इसलिए क्रैश नहीं करना है, क्योंकि प्यू-प्यू की कोई एक आवाज़ लोड नहीं हो सकी.

जैसा कि आपको उम्मीद होगी, अगर एसेट की मौजूद कुंजी मौजूद नहीं है, तो loadAsset() वाला तरीका FlutterError गड़बड़ी भी दे सकता है. आम तौर पर, गेम के साथ बंडल में शामिल नहीं की गई ऐसेट लोड करने की कोशिश करना, आपको ठीक करना चाहिए. इसलिए, यह एक गड़बड़ी है.

अलग-अलग आवाज़ें चलाएँ

आपने देखा होगा कि आपने सिर्फ़ pew1.mp3 फ़ाइल चलाई है, लेकिन ऐसेट डायरेक्ट्री में साउंड के दो अन्य वर्शन मौजूद हैं. जब गेम में एक ही आवाज़ के कई वर्शन होते हैं, तो गेम में खेलने का अनुभव ज़्यादा स्वाभाविक लगता है. ऐसे गेम में, अलग-अलग वर्शन को किसी भी क्रम में या अलग-अलग तरीके से खेला जाता है. उदाहरण के लिए, इस तरह की कार्रवाइयों की वजह से, वीडियो में पैदल चलने वालों की आवाज़ और गोली की आवाज़ एक जैसी नहीं होती. इस वजह से, उनकी आवाज़ नकली होती है.

  • विकल्प के तौर पर, हर बार बटन पर टैप किए जाने पर एक अलग प्यू साउंड चलाने के लिए, कोड में बदलाव करें.

इसका इलस्ट्रेशन

5. लूप में संगीत चलाएं

ज़्यादा देर तक चलने वाली आवाज़ें मैनेज करें

कुछ ऑडियो लंबे समय तक चलने के लिए होते हैं. संगीत इसका सबसे अच्छा उदाहरण है, लेकिन कई गेम ऐसे होते हैं जिनमें माहौल बनाया जाता है. जैसे, गलियारों से निकलती हवा, भिक्षुओं के गुनगुनाने की आवाज़, सदियों पुरानी धातु की आवाज़ या मरीज़ों की खांसते समय की आवाज़.

ये प्लेटाइम वाले ऑडियो सोर्स होते हैं, जिन्हें मिनट में मापा जा सकता है. आपको उनका ट्रैक रखना होगा, ताकि आप ज़रूरत पड़ने पर उन्हें रोक सकें या बंद कर सकें. अक्सर इन फ़ाइलों का बैक अप बड़ी फ़ाइलों के साथ होता है और ये बहुत ज़्यादा मेमोरी का इस्तेमाल कर सकते हैं. इसलिए, इन्हें ट्रैक करने की दूसरी वजह यह भी है कि आप AudioSource इंस्टेंस की ज़रूरत न होने पर इसे नष्ट कर सकें.

इस वजह से, आपको AudioController में नया निजी फ़ील्ड शामिल करना होगा. यह अभी चल रहे गाने के लिए एक हैंडल है. यह लाइन जोड़ें:

lib/audio/audio_controller.dart

...

class AudioController {
  static final Logger _log = Logger('AudioController');

  SoLoud? _soloud;

  SoundHandle? _musicHandle;    // ← Add this.

  ...

संगीत शुरू करें

कम शब्दों में कहें, तो संगीत चलाना, वन-शॉट साउंड चलाने से अलग नहीं है. हालांकि, आपको अब भी पहले assets/music/looped-song.ogg फ़ाइल को AudioSource क्लास के इंस्टेंस के तौर पर लोड करना होगा. इसके बाद, इसे चलाने के लिए SoLoud के play() तरीके का इस्तेमाल करें.

हालांकि, इस बार आपको उस साउंड हैंडल को होल्ड करना होगा जिसे play() तरीका, ऑडियो चलाने के दौरान उसमें बदलाव करने के लिए वापस करता है.

  • अगर आप चाहें, तो AudioController.startMusic() तरीका खुद लागू करें. अगर आपको कुछ जानकारी सही नहीं लगती है, तो कोई समस्या नहीं है. खास बात यह है कि संगीत शुरू करें को चुनने के बाद, संगीत शुरू हो जाएगा.

यहां रेफ़रंस के तौर पर इसे लागू करने की जानकारी दी गई है:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    _musicHandle = await _soloud!.play(musicSource);
  }

...

ध्यान दें कि संगीत फ़ाइल को डिस्क मोड (LoadMode.disk enum) में लोड किया जाता है. इसका सीधा मतलब यह है कि फ़ाइल को ज़रूरत के हिसाब से ही कई हिस्सों में लोड किया जाता है. लंबे समय तक चलने वाले ऑडियो के लिए, आम तौर पर डिस्क मोड में लोड करना सबसे अच्छा होता है. छोटे साउंड इफ़ेक्ट के लिए, उन्हें लोड करना और कंप्रेस करना, ज़्यादा बेहतर विकल्प होता है. यह डिफ़ॉल्ट LoadMode.memory enum है.

हालांकि, आपके सामने कुछ समस्याएं हैं. पहली बात, संगीत बहुत तेज़ है और आवाज़ पर काफ़ी ज़्यादा ज़ोर दिया जा रहा है. ज़्यादातर गेम में, संगीत ज़्यादातर समय बैकग्राउंड में चलता रहता है. इस वजह से, गेम में आवाज़ ज़्यादा जानकारी देने वाली होती है. जैसे, बोली और साउंड इफ़ेक्ट. इसे चलाने के तरीके के वॉल्यूम पैरामीटर का इस्तेमाल करके, आसानी से ठीक किया जा सकता है. उदाहरण के लिए, _soloud!.play(musicSource, volume: 0.6) को 60% वॉल्यूम पर चलाने के लिए आज़माएं. इसके अलावा, _soloud!.setVolume(_musicHandle, 0.6) जैसी किसी सुविधा का इस्तेमाल करके, बाद में किसी भी समय आवाज़ को सेट किया जा सकता है.

दूसरी समस्या यह है कि गाना अचानक रुक जाता है. ऐसा इसलिए है, क्योंकि यह गाना लूप में चलाया जाना चाहिए और लूप का शुरुआती पॉइंट ऑडियो फ़ाइल की शुरुआत नहीं है.

88d2c57fffdfe996.png

यह गेम म्यूज़िक के लिए लोकप्रिय पसंद है. ऐसा इसलिए है, क्योंकि इसका मतलब है कि गाना एक सही इंट्रो के साथ शुरू होता है और ज़रूरत के मुताबिक, लूप पॉइंट के बिना चलता रहता है. जब गेम में चल रहे मौजूदा गाने को हटाना पड़ता है, तो वह गाने को फ़ेड कर देता है.

हालांकि, अच्छी बात यह है कि SoLoud लूप में ऑडियो चलाने के तरीके उपलब्ध कराता है. play() वाला तरीका, looping पैरामीटर के लिए एक बूलियन वैल्यू लेता है. साथ ही, loopingStartAt पैरामीटर के तौर पर लूप के शुरुआती पॉइंट की वैल्यू भी लेता है. इससे मिलने वाला कोड कुछ ऐसा दिखेगा:

lib/audio/audio_controller.dart

...

_musicHandle = await _soloud!.play(
  musicSource,
  volume: 0.6,
  looping: true,
  // ↓ The exact timestamp of the start of the loop.
  loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
);

...

अगर loopingStartAt पैरामीटर को सेट नहीं किया जाता है, तो यह डिफ़ॉल्ट रूप से Duration.zero (दूसरे शब्दों में, ऑडियो फ़ाइल की शुरुआत) पर सेट हो जाता है. अगर आपके पास कोई ऐसा म्यूज़िक ट्रैक है जो बिना किसी परिचय के, लूप में चलने वाला शानदार लूप है, तो पेश है.

  • हर ऑडियो सोर्स से मिलने वाली allInstancesFinished स्ट्रीम को सुनें, ताकि यह पक्का किया जा सके कि उसके पूरा चलने के बाद ऑडियो सोर्स सही तरीके से नष्ट हो जाए. लॉग कॉल जोड़ने पर, startMusic() तरीका कुछ ऐसा दिखेगा:

lib/audio/audio_controller.dart

...

  Future<void> startMusic() async {
    if (_musicHandle != null) {
      if (_soloud!.getIsValidVoiceHandle(_musicHandle!)) {
        _log.info('Music is already playing. Stopping first.');
        await _soloud!.stop(_musicHandle!);
      }
    }
    _log.info('Loading music');
    final musicSource = await _soloud!
        .loadAsset('assets/music/looped-song.ogg', mode: LoadMode.disk);
    musicSource.allInstancesFinished.first.then((_) {
      _soloud!.disposeSource(musicSource);
      _log.info('Music source disposed');
      _musicHandle = null;
    });

    _log.info('Playing music');
    _musicHandle = await _soloud!.play(
      musicSource,
      volume: 0.6,
      looping: true,
      loopingStartAt: const Duration(seconds: 25, milliseconds: 43),
    );
  }

...

फ़ेड साउंड

आपकी अगली समस्या यह है कि संगीत कभी खत्म नहीं होता. आइए, फ़ेड स्टाइल को लागू करते हैं.

फ़ेड लागू करने का एक तरीका यह है कि आप एक ऐसी सुविधा इस्तेमाल करें जिसे एक सेकंड में कई बार कहा जाए. जैसे, Ticker या Timer.periodic. साथ ही, संगीत की आवाज़ को थोड़ी-थोड़ी करके कम किया जा सकता है. यह ठीक रहेगा, लेकिन अभी काफ़ी काम करना है.

अच्छी बात यह है कि SoLoud ऐप्लिकेशन, आग जलाने और मिटाने का आसान तरीका मुहैया कराता है. यहां पर बताया गया है कि पांच सेकंड तक संगीत को फ़ेड किस तरह किया जा सकता है और फिर साउंड इंस्टेंस को कैसे बंद किया जा सकता है, ताकि यह सीपीयू के संसाधनों का ज़रूरत के मुताबिक इस्तेमाल न करे. fadeOutMusic() तरीके को इस कोड से बदलें:

lib/audio/audio_controller.dart

...

  void fadeOutMusic() {
    if (_musicHandle == null) {
      _log.info('Nothing to fade out');
      return;
    }
    const length = Duration(seconds: 5);
    _soloud!.fadeVolume(_musicHandle!, 0, length);
    _soloud!.scheduleStop(_musicHandle!, length);
  }

...

6. इफ़ेक्ट लागू करें

आपके लिए एक सही ऑडियो इंजन होने का एक बड़ा फ़ायदा यह है कि आपको ऑडियो प्रोसेस करने की सुविधा मिलती है. उदाहरण के लिए, कुछ साउंड को रेवर्ब, इक्वलाइज़र या लो-पास फ़िल्टर का इस्तेमाल करके रूट किया जा सकता है.

गेम में इसका इस्तेमाल, अलग-अलग जगहों की आवाज़ें सुनने के लिए किया जा सकता है. उदाहरण के लिए, जंगल में ताली की आवाज़, कंक्रीट बंकर से अलग होती है. एक ओर जहां जंगल इस आवाज़ को फैलने और अवशोषित करने में मदद करता है, वहीं बंकर की खुली दीवारों पर आवाज़ की तरंगें सुनाई देती हैं, जिससे गूंजती सुनाई देती है. इसी तरह, किसी दीवार से सुनाई देने पर लोगों की आवाज़ें अलग तरह की होती हैं. जब ये आवाज़ें ठोस माध्यम से होकर गुज़रती हैं, तो उनकी ज़्यादा फ़्रीक्वेंसी का आसानी से पता लगाया जाता है. इस वजह से, लो-पास फ़िल्टर इफ़ेक्ट बनता है.

एक कमरे में बात करते हुए दो लोगों का इलस्ट्रेशन. ध्वनि तरंगें न सिर्फ़ एक व्यक्ति से दूसरे व्यक्ति तक सीधे जाती हैं, बल्कि दीवारों और छत से भी टकरा जाती हैं.

SoLoud में अलग-अलग तरह के कई ऑडियो इफ़ेक्ट मिलते हैं, जिन्हें ऑडियो के साथ इस्तेमाल किया जा सकता है.

  • यह दिखाने के लिए कि आपका प्लेयर किसी बड़े कमरे, जैसे कि कैथेड्रल या गुफा में है, FilterType.freeverbFilter ईनम का इस्तेमाल करें:

lib/audio/audio_controller.dart

...

  void applyFilter() {
    _soloud!.addGlobalFilter(FilterType.freeverbFilter);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 0, 0.2);
    _soloud!.setFilterParameter(FilterType.freeverbFilter, 2, 0.9);
  }

  void removeFilter() {
    _soloud!.removeGlobalFilter(FilterType.freeverbFilter);
  }

...

यह देखा जा सकता है कि फ़िल्टर की मदद से, ज़्यादा निचले-लेवल वाले इलाके में पहुंचा जा सकता है. फ़िल्टर पैरामीटर सेट करने के लिए, पैरामीटर का इंडेक्स इस्तेमाल किया जाता है. उदाहरण के लिए, फ़्रीवर्ब के वेट पैरामीटर का इंडेक्स 0 है और रूम साइज़ पैरामीटर का इंडेक्स 2 है.

पिछले कोड का इस्तेमाल करके, ये काम किए जा सकते हैं:

  • फ़्रीवर्ब फ़िल्टर को दुनिया भर में या पूरे ऑडियो मिक्स के लिए चालू करें, न कि सिर्फ़ एक आवाज़ के लिए.
  • Wet पैरामीटर को 0.2 पर सेट करें. इसका मतलब है कि तैयार होने वाला ऑडियो 80% ओरिजनल होगा और रीवर्ब इफ़ेक्ट का आउटपुट 20% होगा. अगर इस पैरामीटर को 1.0 पर सेट किया जाता है, तो इससे आपको सिर्फ़ उन आवाज़ों को सुनने की सुविधा मिलेगी जो कमरे की दीवारों से आपके पास वापस आती हैं. इसके अलावा, ऑडियो का कोई भी ओरिजनल ऑडियो इस्तेमाल नहीं किया जाएगा.
  • कमरे का साइज़ पैरामीटर को 0.9 पर सेट करें. इस पैरामीटर में अपनी पसंद के हिसाब से बदलाव किया जा सकता है. इसके अलावा, इसे डाइनैमिक तौर पर भी बदला जा सकता है. 1.0 एक बड़ी गुफा है. वहीं, 0.0 एक बाथरूम है.
  • अगर आपको लगता है कि यह सही है, तो कोड बदलें और इनमें से कोई एक फ़िल्टर या इन फ़िल्टर के कॉम्बिनेशन का इस्तेमाल करें:
  • FilterType.biquadResonantFilter (इसे लो पास फ़िल्टर के तौर पर इस्तेमाल किया जा सकता है)
  • FilterType.eqFilter
  • FilterType.echoFilter
  • FilterType.lofiFilter
  • FilterType.flangerFilter
  • FilterType.bassboostFilter
  • FilterType.waveShaperFilter
  • FilterType.robotizeFilter
  • FilterType.freeverbFilter

7. बधाई हो

आपने एक ऐसा ऑडियो कंट्रोलर लागू किया है जो साउंड चलाता है, संगीत को लूप करता है, और इफ़ेक्ट लागू करता है.

ज़्यादा जानें

  • स्टार्टअप पर पहले से लोड होने वाली आवाज़, किसी क्रम में गाने चलाना या धीरे-धीरे कोई फ़िल्टर लगाना जैसी सुविधाओं के साथ ऑडियो कंट्रोलर को और बेहतर बनाने की कोशिश करें.
  • flutter_soloud का पैकेज दस्तावेज़ पढ़ें.
  • पहले से मौजूद C++ लाइब्रेरी का होम पेज पढ़ें.
  • Dart FFI के बारे में और पढ़ें, जो C++ लाइब्रेरी के साथ इंटरफ़ेस करने के लिए इस्तेमाल की जाने वाली टेक्नोलॉजी है.
  • गेम की ऑडियो प्रोग्रामिंग के बारे में जानने के लिए, गाय सोमबर्ग की बातचीत देखें. (एक लंबा वीडियो भी है.) जब व्यक्ति "मिडलवेयर" के बारे में बात करता है, तो उसका मतलब SoLoud और FMOD जैसी लाइब्रेरी से होता है. बाकी कोड, हर गेम के हिसाब से अलग-अलग होते हैं.
  • अपना गेम बनाएं और उसे रिलीज़ करें.

हेडफ़ोन का इलस्ट्रेशन