Flutter 플러그인 작성 방법

'Flutter 플러그인 작성 방법' Codelab에 오신 것을 환영합니다.

플러그인이란 무엇일까요?

플러그인은 앱에 기능을 더해 주는 소프트웨어입니다. 예를 들어 모바일 앱이 기기의 카메라와 상호작용하도록 할 수 있습니다. 플러그인은 Flutter 생태계에서 중요한 부분을 담당합니다. 먼저 pub.dev에서 필요한 플러그인이 이미 있는지 확인합니다. Flutter 커뮤니티의 회원인 Flutter SDK의 작성자들은 다양한 플러그인을 작성하고 pub.dev에 게시하여 커뮤니티와 공유합니다.

특히 Flutter 즐겨 찾는 패키지 및 플러그인은 꼭 확인해 보세요. Flutter 즐겨찾기 태그는 앱을 빌드할 때 우선적으로 고려해야 하는 플러그인을 나타냅니다.

Flutter를 사용하면 사용자가 크로스 플랫폼인 Dart 라이브러리와 쉽게 상호작용할 수 있지만, 플랫폼별 코드와 상호작용하는 것이 좋을 때도 있습니다. 예를 들어 Dart 라이브러리가 작성되어 있지 않은 데이터베이스와 통신해야 할 경우도 있습니다. Flutter는 플랫폼별 코드와 통신할 수 있는 플러그인 작성 메커니즘을 제공하며, 개발자는 Flutter를 통해 다른 사용자가 이용 가능하도록 플러그인을 pub.dev에 게시할 수도 있습니다.

이 Codelab에서는 iOS 및 Android용 자체 플러그인을 작성하는 방법을 알아봅니다. 호스트 플랫폼에서 오디오를 처리하는 간단한 음악 플러그인을 구현한 다음 플러그인을 사용하여 키보드를 만드는 예시 앱을 만들어 보겠습니다.

다음은 최종 앱의 스크린샷입니다.

f4275505c0be0bd7.png 746b8f48aa63e2ff.png

학습 내용

  • iOS 및 Android용 Flutter 플러그인 작성 방법
  • 플러그인용 API를 만드는 방법
  • 플러그인을 사용하는 앱 작성 방법
  • 다른 사람들이 사용할 수 있도록 플러그인을 게시하는 방법

이 Codelab에서 어떤 내용을 배우고 싶으신가요?

주제를 처음 접하기 때문에 간단하게 내용을 파악하고 싶습니다. 이 주제에 관해 약간 알고 있지만 한 번 더 확인하고 싶습니다. 프로젝트에 사용할 예시 코드를 찾고 있습니다. 구체적인 항목에 대한 설명을 찾고 있습니다.

이 실습을 완료하려면 Flutter SDK편집기라는 두 가지 소프트웨어가 필요합니다. Android 스튜디오 또는 Flutter 및 Dart 플러그인이 설치된 IntelliJ와 같이 선호하는 편집기를 사용하거나 Dart 코드 및 Flutter 확장 프로그램이 포함된 Visual Studio Code를 사용해도 됩니다.

플러그인 개발용 도구 중 일부가 최근 변경되었으므로 이 Codelab에서는 Flutter SDK v1.15.19 이상을 사용한다고 가정합니다. 다음 명령어를 사용하여 버전을 확인할 수 있습니다.

$ flutter doctor

다음 기기 중 하나를 사용하여 Codelab을 실행하세요.

Flutter에는 간편하게 시작할 수 있는 플러그인용 템플릿이 포함되어 있습니다. 플러그인 템플릿을 생성할 때 사용할 언어를 지정할 수 있습니다. 기본값은 Swift(iOS) 및 Kotlin(Android)입니다. 이 Codelab에서는 Objective-C 및 자바를 사용합니다.

작업 디렉터리에서 다음 명령어를 실행하여 플러그인 템플릿을 만들고 null 안전성 기능으로 이전합니다.

$ flutter create --template=plugin --org com.example --template=plugin --platforms=android,ios -a java -i objc plugin_codelab
$ cd plugin_codelab
$ dart migrate --apply-changes
$ cd example
$ flutter pub upgrade
$ dart migrate --apply-changes

이 명령어를 사용하면 다음과 같은 디렉터리 구조가 생성됩니다.

plugin_codelab
├── CHANGELOG.md
├── LICENSE
├── README.md
├── android
│   ├── build.gradle
│   ├── gradle
│   │   └── wrapper
│   │       └── gradle-wrapper.properties
│   ├── gradle.properties
│   ├── local.properties
│   ├── plugin_codelab_android.iml
│   ├── settings.gradle
│   └── src
│       └── main
│           ├── AndroidManifest.xml
│           └── java
│               └── com
│                   └── example
│                       └── plugin_codelab
│                           └── PluginCodelabPlugin.java
├── example
│   ├── README.md
│   ├── android
│   ├── build
│   │   └── ios
│   │       └── Runner.build
│   │           └── Release-iphoneos
│   │               └── Runner.build
│   │                   └── dgph
│   ├── ios
│   ├── lib
│   │   └── main.dart
│   ├── plugin_codelab_example.iml
│   ├── pubspec.lock
│   ├── pubspec.yaml
│   └── test
│       └── widget_test.dart
├── ios
│   ├── Assets
│   ├── Classes
│   │   ├── PluginCodelabPlugin.h
│   │   └── PluginCodelabPlugin.m
│   └── plugin_codelab.podspec
├── lib
│   └── plugin_codelab.dart
├── plugin_codelab.iml
├── pubspec.lock
├── pubspec.yaml
└── test
    └── plugin_codelab_test.dart

다음은 중요한 몇 가지 파일에 관한 설명입니다.

  • pubspec.yaml: 플러그인을 정의하는 YAML 파일이며 플러그인 이름, 종속 항목, 버전, 지원되는 운영체제 등을 지정합니다. 플러그인의 pub.dev 페이지에서 사용됩니다.
  • CHANGELOG.md: 플러그인의 새 버전을 게시하려고 할 때마다 이 마크다운 파일을 업데이트하여 새 버전의 변경사항을 나타내야 합니다.
  • README.md: 이 마크다운 파일은 플러그인의 pub.dev 등록정보 첫 페이지에 표시됩니다. 플러그인의 정의와 사용 방법을 자세하게 설명해야 합니다.
  • lib/plugin_codelab.dart: 플러그인 프런트엔드를 구현하는 Dart 코드입니다. 플러그인 클라이언트는 이 디렉터리의 공개 클래스 및 함수에 액세스할 수 있습니다.
  • android/src/main/java/com/example/plugin_codelab/PluginCodelabPlugin.java: plugin_codelab.dart에 설명된 Android 기능을 구현하는 네이티브 자바 코드입니다.
  • ios/Classes/PluginCodelabPlugin.m: plugin_codelab.dart.에 설명된 iOS 기능을 구현하는 Objective-C 코드입니다(일치하는 헤더 파일도 있음).
  • example/: 이 디렉터리에는 플러그인의 클라이언트가 포함되어 있습니다. 플러그인을 개발하는 동안 이 파일을 수정하여 플러그인이 어떻게 작동하는지 확인할 수 있습니다.
  • example/lib/main.dart: 플러그인을 실행하는 Dart 코드입니다. 여기에서 예시 UI를 빌드하게 됩니다.

다음 안내에 따라 iOS 또는 Android 기기에서 예시 UI를 실행하세요.

$ cd plugin_codelab/example
$ flutter run

다음과 같은 화면이 표시됩니다.

52b7d03a33b9cbfa.png

플러그인 프런트엔드를 구현하기 위해 생성된 코드를 확인합니다.

lib/plugin_codelab.dart

class PluginCodelab {
  static const MethodChannel _channel =
      const MethodChannel('plugin_codelab');

  static Future<String> get platformVersion async {
    final String version =
      await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

cf1e10b838bf60ee.png 알 수 있는 내용

  • PluginCodelab은 플러그인 사용자가 호출하는 클래스입니다.
  • 이 클래스는 Dart 코드가 호스트 플랫폼과 통신할 수 있도록 하는 MethodChannel을 생성합니다.
  • 플러그인의 API에는 속성 getter platformVersion이라는 하나의 메서드만 있습니다. 누군가 Dart에서 이 getter를 호출하면 MethodChannelgetPlatformVersion() 메서드를 호출하고 String이 반환될 때까지 비동기적으로 대기합니다.
  • getPlatformVersion 메시지의 의미를 해석하는 방법은 플랫폼별 코드에 따라 달라지며, 나중에 이에 관해 살펴볼 예정입니다.

example/lib/main.dart

Future<void> initPlatformState() async {
  String platformVersion;
  // Platform messages may fail, so we use a try/catch PlatformException.
  try {
    platformVersion = await PluginCodelab.platformVersion;
  } on PlatformException {
    platformVersion = 'Failed to get platform version.';
  }

  // If the widget was removed from the tree while the asynchronous platform
  // message was in flight, we want to discard the reply rather than calling
  // setState to update our non-existent appearance.
  if (!mounted) return;

  setState(() {
    _platformVersion = platformVersion;
  });
}

cf1e10b838bf60ee.png 알 수 있는 내용

  • 플러그인 클라이언트의 코드입니다.
  • 이 코드는 lib/plugin_codelab.dart에서 정의된 getter를 호출합니다.
  • 이 호출은 try-block에 래핑되어 있습니다. iOS의 플랫폼별 코드가 FlutterError를 반환하거나 자바에서 예외가 발생하면 Dart 쪽에 다시 표시됩니다.

ios/Classes/PluginCodelabPlugin.m

+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"plugin_codelab"
            binaryMessenger:[registrar messenger]];
  PluginCodelabPlugin* instance = [[PluginCodelabPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];
}

cf1e10b838bf60ee.png 알 수 있는 내용

  • 초기화 코드는 새로운 엔진이 설정될 때 호출됩니다.
  • 이 코드를 사용하면 플러그인과 통신하기 위한 채널이 생성됩니다.
  • 여기에 지정된 채널 이름은 lib/plugin_codelab.dart에 정의된 이름과 일치해야 합니다.
  • 자동으로 methodCallDelegate로 설정되면 생성된 인스턴스가 제공된 바이너리 메신저와 연결된 메시지를 수신하게 됩니다.

ios/Classes/PluginCodelabPlugin.m

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"getPlatformVersion" isEqualToString:call.method]) {
    result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  } else {
    result(FlutterMethodNotImplemented);
  }
}

cf1e10b838bf60ee.png 알 수 있는 내용

android/src/main/java/com/example/plugin_codelab/PluginCodelabPlugin.java

@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
  channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "plugin_codelab");
  channel.setMethodCallHandler(this);
}

cf1e10b838bf60ee.png 알 수 있는 내용

  • 이 자바 코드는 Android용 getPlatformVersion()을 구현합니다.

android/src/main/java/com/example/plugin_codelab/PluginCodelabPlugin.java

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  if (call.method.equals("getPlatformVersion")) {
    result.success("Android " + android.os.Build.VERSION.RELEASE);
  } else {
    result.notImplemented();
  }
}

cf1e10b838bf60ee.png 알 수 있는 내용

  • 이 자바 코드는 Dart에서 전송된 메시지를 처리합니다. 이 코드는 iOS 플러그인의 형식과 비슷하지만 약간의 차이가 있습니다.

72ad6e60941a67c6.png특별한 성과를 이루었습니다.

키보드 건반을 눌렀을 때 소리를 내는 신시사이저를 각 플랫폼에 맞게 구현했습니다. 이 코드를 Flutter에 표시할 라이브러리라고 생각하세요. 플러그인을 만들 때는 이 사례에서와 같이 이미 정의한 플랫폼 API를 보유하고 있는 경우가 많습니다.

동일한 기능을 iOS 및 Android용으로 나눠 두 개로 구현했습니다. 이제 구현을 플러그인에서 앱으로 호출할 수 있도록 앱의 일부로 컴파일해야 합니다.

iOS에 추가

프로젝트에 다음 파일을 추가하세요.

이 파일을 ios/Classes 위치에 배치하면 파일이 플러그인용 iOS 빌드의 일부로 컴파일됩니다. ios/plugin_codelab.podspec을 살펴보면 기본적으로 glob을 사용하여 컴파일할 소스를 정의함을 알 수 있습니다. 개발자는 파일을 올바른 위치에 배치하기만 하면 됩니다.

Android에 추가

프로젝트에 다음 파일을 추가하세요.

android/src/main/java/com/example/plugin_codelab/Synth.java

이 자바 파일을 android/src/main/java/com/example 위치에 배치하면 파일이 플러그인용 Android 빌드의 일부로 컴파일됩니다. Gradle 빌드 시스템을 살펴보면 파일을 컴파일하려면 올바른 디렉터리에 배치하기만 하면 된다는 사실을 알 수 있습니다.

신시사이저 인터페이스 설명

신시사이저 인터페이스는 iOS와 Android 모두 유사하며 다음과 같은 4가지 메서드로 구성됩니다.

class Synthesizer {
  void start();
  void stop();
  int keyDown(int key);
  int keyUp(int key);
}

keyUp()keyDown() 메서드는 키보드 건반을 눌렀다 놓을 때 전송되는 이벤트를 나타냅니다. key 인수는 어떤 건반이 눌렸거나 놓여 있는지를 나타내며 키보드의 모든 건반이 열거됩니다. MIDI 표준은 열거된 건반을 정의하며 여기서 60은 가온다의 값으로, 검은색 또는 흰색 건반 1개(반음)마다 1씩 증가합니다. 플러그인에서 이 정의를 사용합니다.

플러그인 만들기의 다음 단계는 Flutter와 호스트 플랫폼 사이에 주고받을 정보에 관해 생각해 보는 것입니다. 이미 정의된 API가 있는 라이브러리를 나타내려고 하는 경우 간단히 해당 인터페이스를 모방하면 됩니다.

이 Codelab에서는 플랫폼별 신시사이저 코드를 제공하므로 Dart 코드에서 인터페이스를 모방할 수 있습니다.

lib/plugin_codelab.dart

import 'dart:async';

import 'package:flutter/services.dart';

class PluginCodelab {
  static const MethodChannel _channel = const MethodChannel('plugin_codelab');

  static Future<String?> get platformVersion async {
    final String? version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  static Future<int?> onKeyDown(int key) async {
    final int? numNotesOn = await _channel.invokeMethod('onKeyDown', [key]);
    return numNotesOn;
  }

  static Future<int?> onKeyUp(int key) async {
    final int? numNotesOn = await _channel.invokeMethod('onKeyUp', [key]);
    return numNotesOn;
  }
}

invokeMethod()의 두 번째 매개변수는 메서드 호출로 전송된 매개변수를 나열합니다.

이제 소리를 내기 위한 라이브러리와 코드를 제어하는 Dart 코드가 플랫폼별로 마련되었지만 라이브러리와 코드가 연결되지 않았습니다. 이러한 Dart 메서드 중 하나를 호출해도 아직 플러그인의 호스트 측을 구현하지 않았기 때문에 '구현되지 않음' 예외만 표시됩니다. 이제 다음 단계입니다.

iOS에서 연결

먼저 플러그인을 수정하여 신시사이저 인스턴스를 만들고 시작합니다.

ios/Classes/PluginCodelabPlugin.m

@implementation PluginCodelabPlugin {
  int _numKeysDown;
  FLRSynthRef _synth;
}
- (instancetype)init {
  self = [super init];
  if (self) {
    _synth = FLRSynthCreate();
    FLRSynthStart(_synth);
  }
  return self;
}

- (void)dealloc {
  FLRSynthDestroy(_synth);
}

그런 다음 채널을 통해 전송된 메시지 처리를 시작합니다.

- (void)handleMethodCall:(FlutterMethodCall *)call
                  result:(FlutterResult)result {
  if ([@"getPlatformVersion" isEqualToString:call.method]) {
    result([@"iOS "
        stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  } else if ([@"onKeyDown" isEqualToString:call.method]) {
    FLRSynthKeyDown(_synth, [call.arguments[0] intValue]);
    _numKeysDown += 1;
    result(@(_numKeysDown));
  } else if ([@"onKeyUp" isEqualToString:call.method]) {
    FLRSynthKeyUp(_synth, [call.arguments[0] intValue]);

    _numKeysDown -= 1;
    result(@(_numKeysDown));
  } else {
    result(FlutterMethodNotImplemented);
  }
}

이제 코드에서 onKeyDownonKeyUp 메시지도 조회합니다. key 인수를 이용하려면 call.arguments에서 가져오세요. 반환된 값은 NSNumber(플랫폼 채널 문서에 설명됨)로 복싱되므로 intValue를 사용해 변환합니다.

완성된 파일인 PluginCodelabPlugin.m을 참고하세요.

Android에서 연결

먼저 플러그인을 수정하여 신시사이저 인스턴스를 만들고 시작합니다.

android/src/main/java/com/example/plugin_codelab/PluginCodelabPlugin.java

public class PluginCodelabPlugin implements FlutterPlugin, MethodCallHandler {
  private MethodChannel channel;
  private Synth synth;
  private static final String channelName = "plugin_codelab";

  private static void setup(PluginCodelabPlugin plugin, BinaryMessenger binaryMessenger) {
    plugin.channel = new MethodChannel(binaryMessenger, channelName);
    plugin.channel.setMethodCallHandler(plugin);
    plugin.synth = new Synth();
    plugin.synth.start();
  }

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    setup(this, flutterPluginBinding.getBinaryMessenger());
  }

그런 다음 채널을 통해 전송된 메시지 처리를 시작합니다.

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  if (call.method.equals("getPlatformVersion")) {
    result.success("Android " + android.os.Build.VERSION.RELEASE);
  } else if (call.method.equals("onKeyDown")) {
    try {
      ArrayList arguments = (ArrayList) call.arguments;
      int numKeysDown = synth.keyDown((Integer) arguments.get(0));
      result.success(numKeysDown);
    } catch (Exception ex) {
      result.error("1", ex.getMessage(), ex.getStackTrace());
    }
  } else if (call.method.equals("onKeyUp")) {
    try {
      ArrayList arguments = (ArrayList) call.arguments;
      int numKeysDown = synth.keyUp((Integer) arguments.get(0));
      result.success(numKeysDown);
    } catch (Exception ex) {
      result.error("1", ex.getMessage(), ex.getStackTrace());
    }
  } else {
    result.notImplemented();
  }
}

iOS와 마찬가지로 코드에서 이제 onKeyDownonKeyUp 메시지를 조회합니다. 여기에서 arguments.get()을 사용하여 key 값도 추출하세요. 플러그인은 Android에서 실행될 때 발생할 수 있는 예외를 모두 처리해야 합니다.

완성된 파일인 PluginCodelabPlugin.java를 참고하세요.

플러그인에서 연결 작업이 모두 완료되었으므로 실제로 작동하는 것을 확인해 보기 위하여 간단한 키보드 UI 예제 앱을 구현해 보겠습니다.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:plugin_codelab/plugin_codelab.dart';

enum _KeyType { Black, White }

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeRight])
      .then((_) {
    runApp(new MyApp());
  });
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String? _platformVersion = 'Unknown';

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String? platformVersion;
    try {
      platformVersion = await PluginCodelab.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  void _onKeyDown(int key) {
    print("key down:$key");
    PluginCodelab.onKeyDown(key).then((value) => print(value));
  }

  void _onKeyUp(int key) {
    print("key up:$key");
    PluginCodelab.onKeyUp(key).then((value) => print(value));
  }

  Widget _makeKey({@required _KeyType keyType, @required int key}) {
    return AnimatedContainer(
      height: 200,
      width: 44,
      duration: Duration(seconds: 2),
      curve: Curves.easeIn,
      child: Material(
        color: keyType == _KeyType.White
            ? Colors.white
            : Color.fromARGB(255, 60, 60, 80),
        child: InkWell(
          onTap: () => _onKeyUp(key),
          onTapDown: (details) => _onKeyDown(key),
          onTapCancel: () => _onKeyUp(key),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Color.fromARGB(255, 250, 30, 0),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              Text('Running on: $_platformVersion\n'),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: <Widget>[
                  _makeKey(keyType: _KeyType.White, key: 60),
                  _makeKey(keyType: _KeyType.Black, key: 61),
                  _makeKey(keyType: _KeyType.White, key: 62),
                  _makeKey(keyType: _KeyType.Black, key: 63),
                  _makeKey(keyType: _KeyType.White, key: 64),
                  _makeKey(keyType: _KeyType.White, key: 65),
                  _makeKey(keyType: _KeyType.Black, key: 66),
                  _makeKey(keyType: _KeyType.White, key: 67),
                  _makeKey(keyType: _KeyType.Black, key: 68),
                  _makeKey(keyType: _KeyType.White, key: 69),
                  _makeKey(keyType: _KeyType.Black, key: 70),
                  _makeKey(keyType: _KeyType.White, key: 71),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

다음 사항에 유의하세요.

  • 플러그인을 사용하려면 'package:plugin_codelab/plugin_codelab.dart'를 가져와야 합니다. 플러그인에서 예시 UI의 종속 항목은 작업이 진행되는 example/pubspec.yaml,에서 정의됩니다.
  • main(),에서는 전체 키보드가 화면에 맞게 표시되도록 방향이 가로 모드로 강제 설정됩니다.
  • _onKeyDown()_onKeyUp() 메서드는 모두 이전 단계에서 설계된 플러그인 API의 클라이언트입니다.
  • 이 코드는 상호작용형 직사각형인 InkWell을 사용하여 개별 건반을 그립니다.

앱을 실행하여 키보드가 제대로 작동하는지 확인하세요.

cd example
flutter run

다음과 같이 표시되어야 합니다.

f4275505c0be0bd7.png

축하합니다. iOS 및 Android용 Flutter 플러그인을 만드는 데 성공했습니다. 이제 멋진 키보드를 사용해 즉석에서 음악을 만들 수 있습니다. 완성된 프로젝트는 비교 참고용으로 https://github.com/flutter/codelabs/tree/master/plugin_codelab에서 다운로드 가능합니다.

다음 단계

  • 엔드 투 엔드 테스트를 추가합니다. Flutter팀은 엔드 투 엔드 통합 테스트를 만드는 라이브러리인 e2e를 제공합니다.
  • pub.dev에 게시합니다. 플러그인을 만든 후에는 다른 사용자가 이용할 수 있도록 온라인으로 공유해 보세요. 플러그인 패키지 개발에서 플러그인을 pub.dev에 게시하는 방법에 관한 전체 문서를 확인할 수 있습니다.

신시사이저 확장

재미를 위해 신시사이저를 좀 더 개선하고 싶다면 다음의 몇 가지 추가 단계를 확인해 보세요.

  • 현재의 신시사이저는 사인파를 생성합니다. 톱니파를 생성하도록 빌드하는 건 어떨까요?
  • 건반을 누를 때와 놓을 때 짧은 소리가 난다는 걸 눈치챘나요? 진동자가 갑자기 켜지고 꺼질 때 나는 소리인데, 보통 신시사이저에서는 진폭 엔벨로프라는 것을 사용해 이를 해결합니다.
  • 지금은 한 번에 하나의 건반 소리만 재생할 수 있으며 이를 모노포닉이라고 합니다. 하지만 실제 피아노는 폴리포닉입니다.