From 7db24d18d4d020800005be0e12e7ea827166c7fc Mon Sep 17 00:00:00 2001 From: kevvz Date: Wed, 28 Aug 2024 17:42:37 -0700 Subject: [PATCH 1/2] ss --- Friend/firmware/firmware_v1.0/Kconfig | 4 + .../prj_xiao_ble_sense_devkitv2-adafruit.conf | 2 + Friend/firmware/firmware_v1.0/src/speaker.c | 130 ++++++++++++++++++ Friend/firmware/firmware_v1.0/src/speaker.h | 5 + Friend/firmware/firmware_v1.0/src/transport.c | 10 +- 5 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 Friend/firmware/firmware_v1.0/src/speaker.c create mode 100644 Friend/firmware/firmware_v1.0/src/speaker.h diff --git a/Friend/firmware/firmware_v1.0/Kconfig b/Friend/firmware/firmware_v1.0/Kconfig index 3c2b198009..e60ad2fe5a 100644 --- a/Friend/firmware/firmware_v1.0/Kconfig +++ b/Friend/firmware/firmware_v1.0/Kconfig @@ -10,7 +10,11 @@ config OFFLINE_STORAGE default n config ACCELEROMETER bool "Accelerometer Support" + default n config ENABLE_BUTTON bool "Button support on Devkit2" default n +config ENABLE_SPEAKER + bool "Enable the speaker!!" + default n endmenu diff --git a/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf b/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf index 3ff66375af..216c9f53e9 100644 --- a/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf +++ b/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf @@ -157,3 +157,5 @@ CONFIG_SPI_NRFX=y CONFIG_ACCELEROMETER=y #ENABLE THE BUTTON! CONFIG_ENABLE_BUTTON=y +#ENABLE THE SPEAKER +CONFIG_ENABLE_SPEAKER=y \ No newline at end of file diff --git a/Friend/firmware/firmware_v1.0/src/speaker.c b/Friend/firmware/firmware_v1.0/src/speaker.c new file mode 100644 index 0000000000..702fc12cbd --- /dev/null +++ b/Friend/firmware/firmware_v1.0/src/speaker.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "lut.c" +LOG_MODULE_REGISTER(speaker, CONFIG_LOG_DEFAULT_LEVEL); + +#define MAX_BLOCK_SIZE 25000 //24000 * 2 +#define BLOCK_COUNT 2 +#define SAMPLE_FREQUENCY 8000 +#define NUMBER_OF_CHANNELS 2 +#define PACKET_SIZE 400 +#define WORD_SIZE 16 +K_MEM_SLAB_DEFINE_STATIC(mem_slab, MAX_BLOCK_SIZE, BLOCK_COUNT, 4); +static void* rx_buffer; +static void* buzz_buffer; +static int16_t *ptr2; +static int16_t *clear_ptr; +static struct device *speaker; +static uint16_t current_length; +static uint16_t offset; +int speaker_init() { + const struct device *mic = device_get_binding("I2S_0"); + speaker = mic; + if (!device_is_ready(mic)) { + LOG_ERR("Speaker device is not supported : %s", mic->name); + return 0; + } + struct i2s_config config = { + .word_size= WORD_SIZE, //how long is one left/right word. + .channels = NUMBER_OF_CHANNELS, //how many words in a frame 2 + .format = I2S_FMT_DATA_FORMAT_LEFT_JUSTIFIED, //format + // .format = I2S_FMT_DATA_FORMAT_I2S, + .options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER | I2S_OPT_BIT_CLK_GATED, //how to configure the mclock + .frame_clk_freq = SAMPLE_FREQUENCY, /* Sampling rate */ + .mem_slab = &mem_slab,/* Memory slab to store rx/tx data */ + .block_size = MAX_BLOCK_SIZE,/* size of ONE memory block in bytes */ + .timeout = -1, /* Number of milliseconds to wait in case Tx queue is full or RX queue is empty, or 0, or SYS_FOREVER_MS */ + }; + int err = i2s_configure(mic, I2S_DIR_TX, &config); + if (err < 0) { + LOG_INF("Failed to configure Microphone (%d)", err); + return 0; + } + err = k_mem_slab_alloc(&mem_slab, &rx_buffer, K_MSEC(200)); + if (err) { + LOG_INF("Failed to allocate memory again(%d)", err); + return 0; + } + + err = k_mem_slab_alloc(&mem_slab, &buzz_buffer, K_MSEC(200)); + if (err) { + LOG_INF("Failed to allocate memory again(%d)", err); + return 0; + } + + memset(rx_buffer, 0, MAX_BLOCK_SIZE); + int16_t *noise = (int16_t*)buzz_buffer; + + memset(buzz_buffer, 0, MAX_BLOCK_SIZE); + return 1; +} + +uint16_t speak(uint16_t len, const void *buf) { + + uint16_t amount = 0; + amount = len; + if (len == 4) //if stage 1 + { + current_length = ((uint32_t *)buf)[0]; + LOG_INF("About to write %u bytes", current_length); + ptr2 = (int16_t *)rx_buffer; + clear_ptr = (int16_t *)rx_buffer; + } + else { //if not stage 1 + if (current_length > PACKET_SIZE) { + LOG_INF("Data length: %u", len); + current_length = current_length - PACKET_SIZE; + LOG_INF("remaining data: %u", current_length); + + for (int i = 0; i < len/2; i++) { + *ptr2++ = ((int16_t *)buf)[i]; + ptr2++; + + } + offset = offset + len; + } + else if (current_length < PACKET_SIZE) { + LOG_INF("entered the final stretch"); + LOG_INF("Data length: %u", len); + current_length = current_length - len; + LOG_INF("remaining data: %u", current_length); + // memcpy(rx_buffer+offset, buf, len); + for (int i = 0; i < len/2; i++) { + *ptr2++ = ((int16_t *)buf)[i]; + ptr2++; + } + offset = offset + len; + LOG_INF("offset: %u", offset); + + + i2s_write(speaker, rx_buffer, MAX_BLOCK_SIZE); + i2s_trigger(speaker, I2S_DIR_TX, I2S_TRIGGER_START);// calls are probably non blocking + i2s_trigger(speaker, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + + //clear the buffer + + k_sleep(K_MSEC(4000)); + memset(clear_ptr, 0, MAX_BLOCK_SIZE); + } + + } + return amount; +} + + +void buzz() { + i2s_write(speaker, buzz_buffer, MAX_BLOCK_SIZE); + i2s_trigger(speaker, I2S_DIR_TX, I2S_TRIGGER_START);// calls are probably non blocking + i2s_trigger(speaker, I2S_DIR_TX, I2S_TRIGGER_DRAIN); + k_msleep(4000); +} \ No newline at end of file diff --git a/Friend/firmware/firmware_v1.0/src/speaker.h b/Friend/firmware/firmware_v1.0/src/speaker.h new file mode 100644 index 0000000000..f698f2d255 --- /dev/null +++ b/Friend/firmware/firmware_v1.0/src/speaker.h @@ -0,0 +1,5 @@ +#pragma once +#include +int speaker_init(); +uint16_t speak(); +void buzz(); \ No newline at end of file diff --git a/Friend/firmware/firmware_v1.0/src/transport.c b/Friend/firmware/firmware_v1.0/src/transport.c index 5b5f24d186..8057252301 100644 --- a/Friend/firmware/firmware_v1.0/src/transport.c +++ b/Friend/firmware/firmware_v1.0/src/transport.c @@ -13,7 +13,7 @@ #include "utils.h" #include "btutils.h" #include "lib/battery/battery.h" - +#include "speaker.h" #include @@ -28,6 +28,8 @@ uint16_t current_package_index = 0; // Internal // +static ssize_t audio_data_write_handler(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags); + static struct bt_conn_cb _callback_references; struct bt_conn *current_connection = NULL; static void audio_ccc_config_changed_handler(const struct bt_gatt_attr *attr, uint16_t value); @@ -49,12 +51,18 @@ static ssize_t dfu_control_point_write_handler(struct bt_conn *conn, const struc static struct bt_uuid_128 audio_service_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10000, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214)); static struct bt_uuid_128 audio_characteristic_data_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10001, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214)); static struct bt_uuid_128 audio_characteristic_format_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10002, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214)); +static struct bt_uuid_128 audio_characteristic_speaker_uuid = BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x19B10003, 0xE8F2, 0x537E, 0x4F6C, 0xD104768A1214)); static struct bt_gatt_attr audio_service_attr[] = { BT_GATT_PRIMARY_SERVICE(&audio_service_uuid), BT_GATT_CHARACTERISTIC(&audio_characteristic_data_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, audio_data_read_characteristic, NULL, NULL), BT_GATT_CCC(audio_ccc_config_changed_handler, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), BT_GATT_CHARACTERISTIC(&audio_characteristic_format_uuid.uuid, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, audio_codec_read_characteristic, NULL, NULL), + #ifdef CONFIG_ENABLE_SPEAKER + BT_GATT_CHARACTERISTIC(&audio_characteristic_speaker_uuid.uuid, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_WRITE, NULL, audio_data_write_handler, NULL), + BT_GATT_CCC(audio_ccc_config_changed_handler, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), // + + }; static struct bt_gatt_service audio_service = BT_GATT_SERVICE(audio_service_attr); From 945178de24df4dccce9efdad759a14d837036efb Mon Sep 17 00:00:00 2001 From: kevvz Date: Wed, 28 Aug 2024 19:22:56 -0700 Subject: [PATCH 2/2] rebased changes --- Friend/firmware/firmware_v1.0/CMakeLists.txt | 1 + .../prj_xiao_ble_sense_devkitv2-adafruit.conf | 32 ++-- Friend/firmware/firmware_v1.0/src/speaker.c | 1 - Friend/firmware/firmware_v1.0/src/transport.c | 25 ++- Friend/firmware/testing/discover_devices.py | 9 + .../firmware/testing/play_sound_on_friend.py | 157 ++++++++++++++++++ 6 files changed, 204 insertions(+), 21 deletions(-) create mode 100644 Friend/firmware/testing/discover_devices.py create mode 100644 Friend/firmware/testing/play_sound_on_friend.py diff --git a/Friend/firmware/firmware_v1.0/CMakeLists.txt b/Friend/firmware/firmware_v1.0/CMakeLists.txt index 5e39b0078a..c50790ea59 100644 --- a/Friend/firmware/firmware_v1.0/CMakeLists.txt +++ b/Friend/firmware/firmware_v1.0/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(app PRIVATE src/codec.c src/lib/battery/battery.c src/button.c + src/speaker.c ) target_sources_ifdef(CONFIG_CODEC_OPUS app PRIVATE diff --git a/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf b/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf index 216c9f53e9..9b594ff795 100644 --- a/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf +++ b/Friend/firmware/firmware_v1.0/prj_xiao_ble_sense_devkitv2-adafruit.conf @@ -61,16 +61,16 @@ CONFIG_BT_AUTO_PHY_UPDATE=y # Debug # Enable the lines below to enable debug logs via UART/USB -CONFIG_DEBUG=y -CONFIG_DEBUG_OPTIMIZATIONS=y -CONFIG_LOG=y -CONFIG_LOG_PRINTK=y -CONFIG_LOG_MODE_IMMEDIATE=y -CONFIG_SERIAL=y -CONFIG_UART_CONSOLE=y -CONFIG_LOG_BACKEND_UART=y -CONFIG_LOG_BACKEND_UART_OUTPUT_TEXT=y -CONFIG_LOG_DEFAULT_LEVEL=3 +# CONFIG_DEBUG=y +# CONFIG_DEBUG_OPTIMIZATIONS=y +# CONFIG_LOG=y +# CONFIG_LOG_PRINTK=y +# CONFIG_LOG_MODE_IMMEDIATE=y +# CONFIG_SERIAL=y +# CONFIG_UART_CONSOLE=y +# CONFIG_LOG_BACKEND_UART=y +# CONFIG_LOG_BACKEND_UART_OUTPUT_TEXT=y +# CONFIG_LOG_DEFAULT_LEVEL=3 # Debug (This value breaks some builds) # CONFIG_ASSERT=y @@ -119,12 +119,12 @@ CONFIG_OFFLINE_STORAGE=y # CONFIG_SPI_SDHC=y # SD Card Support -CONFIG_DISK_DRIVER_SDMMC=y -CONFIG_MMC_STACK=y -CONFIG_SDMMC_STACK=y -CONFIG_SPI=y -CONFIG_SDHC=y -CONFIG_SPI_SDHC=y +# CONFIG_DISK_DRIVER_SDMMC=y +# CONFIG_MMC_STACK=y +# CONFIG_SDMMC_STACK=y +# CONFIG_SPI=y +# CONFIG_SDHC=y +# CONFIG_SPI_SDHC=y # File System CONFIG_FILE_SYSTEM=y diff --git a/Friend/firmware/firmware_v1.0/src/speaker.c b/Friend/firmware/firmware_v1.0/src/speaker.c index 702fc12cbd..3af270ffa8 100644 --- a/Friend/firmware/firmware_v1.0/src/speaker.c +++ b/Friend/firmware/firmware_v1.0/src/speaker.c @@ -10,7 +10,6 @@ #include #include -#include "lut.c" LOG_MODULE_REGISTER(speaker, CONFIG_LOG_DEFAULT_LEVEL); #define MAX_BLOCK_SIZE 25000 //24000 * 2 diff --git a/Friend/firmware/firmware_v1.0/src/transport.c b/Friend/firmware/firmware_v1.0/src/transport.c index 8057252301..6421eaf405 100644 --- a/Friend/firmware/firmware_v1.0/src/transport.c +++ b/Friend/firmware/firmware_v1.0/src/transport.c @@ -31,7 +31,6 @@ uint16_t current_package_index = 0; static ssize_t audio_data_write_handler(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags); static struct bt_conn_cb _callback_references; -struct bt_conn *current_connection = NULL; static void audio_ccc_config_changed_handler(const struct bt_gatt_attr *attr, uint16_t value); static ssize_t audio_data_read_characteristic(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset); static ssize_t audio_codec_read_characteristic(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset); @@ -58,11 +57,11 @@ static struct bt_gatt_attr audio_service_attr[] = { BT_GATT_CHARACTERISTIC(&audio_characteristic_data_uuid.uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ, audio_data_read_characteristic, NULL, NULL), BT_GATT_CCC(audio_ccc_config_changed_handler, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), BT_GATT_CHARACTERISTIC(&audio_characteristic_format_uuid.uuid, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, audio_codec_read_characteristic, NULL, NULL), - #ifdef CONFIG_ENABLE_SPEAKER +#ifdef CONFIG_ENABLE_SPEAKER BT_GATT_CHARACTERISTIC(&audio_characteristic_speaker_uuid.uuid, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_WRITE, NULL, audio_data_write_handler, NULL), BT_GATT_CCC(audio_ccc_config_changed_handler, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), // - - +#endif + }; static struct bt_gatt_service audio_service = BT_GATT_SERVICE(audio_service_attr); @@ -239,6 +238,14 @@ static ssize_t audio_codec_read_characteristic(struct bt_conn *conn, const struc return bt_gatt_attr_read(conn, attr, buf, len, offset, value, sizeof(value)); } +static ssize_t audio_data_write_handler(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) + { + uint16_t amount = 0; + bt_gatt_notify(conn, attr, &amount, sizeof(amount)); + amount = speak(len, buf); + return len; + } + // // DFU Service Handlers // @@ -595,6 +602,16 @@ int transport_start() button_init(); register_button_service(); +#endif + +#ifdef CONFIG_ENABLE_SPEAKER + +err = speaker_init(); +if(!err) { + LOG_ERR("Speaker failed to start"); + return 0; +} + #endif // Start advertising diff --git a/Friend/firmware/testing/discover_devices.py b/Friend/firmware/testing/discover_devices.py new file mode 100644 index 0000000000..b78cbf582f --- /dev/null +++ b/Friend/firmware/testing/discover_devices.py @@ -0,0 +1,9 @@ +import asyncio +from bleak import BleakScanner + +async def main(): + devices = await BleakScanner.discover() + for d in devices: + print(d) + +asyncio.run(main()) \ No newline at end of file diff --git a/Friend/firmware/testing/play_sound_on_friend.py b/Friend/firmware/testing/play_sound_on_friend.py new file mode 100644 index 0000000000..56d5521119 --- /dev/null +++ b/Friend/firmware/testing/play_sound_on_friend.py @@ -0,0 +1,157 @@ +import argparse +import os +from dotenv import load_dotenv +import wave +from deepgram import( DeepgramClient,SpeakOptions,) + +import asyncio +import bleak +import numpy as np +from bleak import BleakClient + +load_dotenv() + +filename = "output2.wav" + +remaining_bytes = 0 +remaining_bytes_b = bytearray() +packet_size = 400 +total_offset = 0 +device_id = "3CE1CE0A-A629-2E92-D708-E49E71045D07" #Please enter the id of your device (that is, the device id used to connect to your BT device here) +deepgram_api_id="f2e9ebf2f223ae423c88bf601ce1a157699d3005" #enter your deepgram id here +audio_write_characteristic_uuid = "19B10003-E8F2-537E-4F6C-D104768A1214" #dont change this +MAX_ALLOWED_SAMPLES = 50000 + +gain = 5 + +async def main(): + global remaining_bytes + global audio_write_characteristic_uuid + parser = argparse.ArgumentParser(description="Accept a string and print it") + parser.add_argument("input_string", type=str, help="The string to be printed") + args = parser.parse_args() + print(args.input_string) #stage one: get the input string + SPEAK_OPTIONS = {"text": args.input_string} + + try: + # STEP 1: Create a Deepgram client using the API key from environment variables + deepgram = DeepgramClient(api_key=deepgram_api_id) #INSERT YOUT DEEPGRAM KEY HERE + + # STEP 2: Configure the options (such as model choice, audio configuration, etc.) + options = SpeakOptions( + model="aura-stella-en", + encoding="linear16", + container="wav" + ) + + # STEP 3: Call the save method on the speak property + response = deepgram.speak.v("1").save(filename, SPEAK_OPTIONS, options) + print(response.to_json(indent=4)) + + except Exception as e: + print(f"Exception: {e}") + + file_path = 'output2.wav' + +# Open the wav file + with wave.open(file_path, 'rb') as wav_file: + # Extract raw audio frames + frames = wav_file.readframes(wav_file.getnframes()) + # Get the number of channels + num_channels = wav_file.getnchannels() + # Get the sample width in bytes + sample_width = wav_file.getsampwidth() + # Get the frame rate (samples per second) + frame_rate = wav_file.getframerate() + # Convert the audio frames to a numpy array + audio_data = np.frombuffer(frames, dtype=np.int16) + # one channel, 16 bit, 24000 + print("Channels:", num_channels) + print("Sample Width (bytes):", sample_width) + print("Frame Rate (samples per second):", frame_rate) + print("Audio Data:", audio_data) + print("Audio length: ", len(audio_data)) + + # Select every third sample for down-sampling + third_samples = audio_data[::3] * gain + + # New sample rate (original rate divided by 3) + new_sample_rate = frame_rate // 3 + + # Convert third_samples to bytes for writing to a new wav file + third_samples_bytes = third_samples.tobytes() + + # Write the resampled audio data to a new WAV file + new_file_path = 'output_80002.wav' + with wave.open(new_file_path, 'wb') as new_wav_file: + new_wav_file.setnchannels(num_channels) + new_wav_file.setsampwidth(sample_width) + new_wav_file.setframerate(new_sample_rate) + new_wav_file.writeframes(third_samples_bytes) + + print(f"Resampled audio written to {new_file_path}") + + # Write the third samples to a text file + output_file_path = 'every_third_sample2.txt' + with open(output_file_path, 'w') as f_: + for samples in third_samples: + if samples != '': + f_.write(f"{samples}\n") + + print(f"Every third sample written to {output_file_path}") + f = open('every_third_sample2.txt','r').read() + f = np.array(list(map(int,f.split('\n')[:-1]))).astype(np.int16).tobytes() + + remaining_bytes = np.array([len(f)]).astype(np.uint32)[0] + remaining_bytes_b = np.array([len(f)]).astype(np.uint32).tobytes() + if (remaining_bytes> MAX_ALLOWED_SAMPLES): + print("Array too large to play. Exitting") + exit() + print("Number of samples about to be sent: ",remaining_bytes) + print("about to start...") + async with BleakClient(device_id) as client: + print(client.address) + offset_ = client.mtu_size + print(offset_) + temp = client.services + for service in temp: + print(service) + + async def on_notify(sender: bleak.BleakGATTCharacteristic, data: bytearray): + global remaining_bytes + global total_offset + global remaining_bytes_b + global packet_size + global audio_write_characteristic_uuid + print(np.frombuffer(data,dtype=np.int16)[0]) + if (remaining_bytes > packet_size): + + final_offset = total_offset + total_offset = packet_size + total_offset + remaining_bytes = remaining_bytes - packet_size + print("sending indexes %d to %d",final_offset,final_offset+packet_size) + await client.write_gatt_char(audio_write_characteristic_uuid, f[final_offset:(final_offset+packet_size)], response=True) + + elif (remaining_bytes > 0 and remaining_bytes <= packet_size): + print('almost done') + print(remaining_bytes) + start_idx = total_offset + total_offset = remaining_bytes+ total_offset + offset_ = remaining_bytes + remaining_bytes = 0 + print("sending indexes",start_idx,start_idx+offset_) + await client.write_gatt_char(audio_write_characteristic_uuid, f[start_idx:(start_idx+offset_)], response=True) + else: + print('done') + print(total_offset) + print('Shutting down') + exit() + await client.start_notify(audio_write_characteristic_uuid, on_notify) + await asyncio.sleep(1) + await client.write_gatt_char(audio_write_characteristic_uuid, remaining_bytes_b, response=True) + await asyncio.sleep(1) + while True: + await asyncio.sleep(1) + +if __name__ == "__main__": + asyncio.run(main())