diff --git a/boards/default_src/python_pyodide/blocks/sound/play/play_frequency.js b/boards/default_src/python_pyodide/blocks/sound/play/play_frequency.js index db7fec4d..92610f62 100644 --- a/boards/default_src/python_pyodide/blocks/sound/play/play_frequency.js +++ b/boards/default_src/python_pyodide/blocks/sound/play/play_frequency.js @@ -7,12 +7,12 @@ export const sound_play_frequency = { .appendField(Blockly.Msg.MIXLY_SOUND_PLAY) .appendField(Blockly.Msg.MIXLY_SOUND_FREQUENCY); this.appendValueInput("FREQUENCY") - .setCheck(null) + .setCheck(Number) .setAlign(Blockly.ALIGN_RIGHT); this.appendDummyInput() .appendField(Blockly.Msg.MIXLY_SOUND_DURATION); this.appendValueInput("DURATION") - .setCheck(null) + .setCheck(Number) .setAlign(Blockly.ALIGN_RIGHT); this.setPreviousStatement(true); this.setNextStatement(true); diff --git a/boards/default_src/python_pyodide/blocks/sound/play/play_frequency_no_duration.js b/boards/default_src/python_pyodide/blocks/sound/play/play_frequency_no_duration.js index eb9c43fc..49ddfc42 100644 --- a/boards/default_src/python_pyodide/blocks/sound/play/play_frequency_no_duration.js +++ b/boards/default_src/python_pyodide/blocks/sound/play/play_frequency_no_duration.js @@ -4,7 +4,7 @@ export const sound_play_frequency_no_duration = { init: function() { this.setColour('#acc159'); this.appendValueInput("FREQUENCY") - .setCheck(null) + .setCheck(Number) .appendField(Blockly.Msg.MIXLY_SOUND_PLAY_FREQUENCY_NO_DURATION); this.setPreviousStatement(true); this.setNextStatement(true); diff --git a/boards/default_src/python_pyodide/blocks/sound/play/play_note_list.js b/boards/default_src/python_pyodide/blocks/sound/play/play_note_list.js index 48cf24c8..60c14062 100644 --- a/boards/default_src/python_pyodide/blocks/sound/play/play_note_list.js +++ b/boards/default_src/python_pyodide/blocks/sound/play/play_note_list.js @@ -4,8 +4,7 @@ export const sound_play_note_list = { init: function() { this.setColour('#acc159'); this.appendDummyInput() - .appendField(Blockly.Msg.MIXLY_SOUND_PLAY_NOTE_LIST); - this.appendDummyInput() + .appendField(Blockly.Msg.MIXLY_SOUND_PLAY_NOTE_LIST) .appendField(new Blockly.FieldDropdown([ ["DADADADUM", "DADADADUM"], ["BIRTHDAY", "BIRTHDAY"], diff --git a/boards/default_src/python_pyodide/blocks/sound/play/sound_note.js b/boards/default_src/python_pyodide/blocks/sound/play/sound_note.js index 82cc6a47..1a31a3b2 100644 --- a/boards/default_src/python_pyodide/blocks/sound/play/sound_note.js +++ b/boards/default_src/python_pyodide/blocks/sound/play/sound_note.js @@ -1,23 +1,24 @@ import * as Blockly from 'blockly/core'; export const sound_note = { - init: function() { + init: function () { this.setColour('#acc159'); this.appendDummyInput() .appendField(new Blockly.FieldDropdown([ - ["NOTE_B3", "NOTE_B3"], - ["NOTE_C4", "NOTE_C4"], - ["NOTE_D4", "NOTE_D4"], - ["NOTE_E4", "NOTE_E4"], - ["NOTE_F4", "NOTE_F4"], - ["NOTE_G4", "NOTE_G4"], - ["NOTE_A4", "NOTE_A4"], - ["NOTE_B4", "NOTE_B4"], - ["NOTE_C5", "NOTE_C5"], - ["NOTE_D5", "NOTE_D5"], - ["NOTE_E5", "NOTE_E5"], - ["NOTE_F5", "NOTE_F5"], - ["NOTE_G5", "NOTE_G5"] + ["NOTE_A4", "440"], + ["NOTE_B3", "247"], + ["NOTE_C4", "262"], + ["NOTE_D4", "294"], + ["NOTE_E4", "330"], + ["NOTE_F4", "349"], + ["NOTE_G4", "392"], + ["NOTE_A4", "440"], + ["NOTE_B4", "494"], + ["NOTE_C5", "523"], + ["NOTE_D5", "587"], + ["NOTE_E5", "659"], + ["NOTE_F5", "698"], + ["NOTE_G5", "784"] ]), "NOTE"); this.setOutput(true, null); this.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND); diff --git a/boards/default_src/python_pyodide/generators/sound/play/play_frequency.js b/boards/default_src/python_pyodide/generators/sound/play/play_frequency.js index 5e7cfc0d..e2b8dda4 100644 --- a/boards/default_src/python_pyodide/generators/sound/play/play_frequency.js +++ b/boards/default_src/python_pyodide/generators/sound/play/play_frequency.js @@ -1,75 +1,10 @@ -function hasPlayWaitBefore(block) { - let currentBlock = block.getPreviousBlock(); - while (currentBlock) { - if (currentBlock.type === 'sound_play_wait') { - return true; - } - currentBlock = currentBlock.getPreviousBlock(); - } - return false; -} - -export const sound_play_frequency = function(_block, generator) { +export const sound_play_frequency = function (_block, generator) { if (!generator.definitions_['import_sound']) { generator.definitions_['import_sound'] = 'import sound'; } - const frequencyInput = _block.getInputTargetBlock("FREQUENCY"); - const durationInput = _block.getInputTargetBlock("DURATION"); - let frequencyCode, durationCode; + const frequencyInput = generator.valueToCode(this, "FREQUENCY", generator.ORDER_ATOMIC); + const durationInput = generator.valueToCode(this, "DURATION", generator.ORDER_ATOMIC); - if (frequencyInput) { - try { - if (frequencyInput.type === "sound_note") { - const note = frequencyInput.getFieldValue("NOTE") || "NOTE_A4"; - const noteFrequencies = { - "NOTE_B3": 247, - "NOTE_C4": 262, - "NOTE_D4": 294, - "NOTE_E4": 330, - "NOTE_F4": 349, - "NOTE_G4": 392, - "NOTE_A4": 440, - "NOTE_B4": 494, - "NOTE_C5": 523, - "NOTE_D5": 587, - "NOTE_E5": 659, - "NOTE_F5": 698, - "NOTE_G5": 784 - }; - frequencyCode = noteFrequencies[note] || 440; - } else if (frequencyInput.type === "math_number") { - const numValue = frequencyInput.getFieldValue("NUM"); - frequencyCode = numValue || "440"; - } else { - frequencyCode = generator.valueToCode(frequencyInput, "FREQUENCY", generator.ORDER_ATOMIC); - } - } catch (error) { - console.warn("生成频率代码时出错:", error); - frequencyCode = "440"; - } - } else { - frequencyCode = "440"; - } - - if (durationInput) { - try { - if (durationInput.type === "math_number") { - const numValue = durationInput.getFieldValue("NUM"); - durationCode = numValue || "1000"; - } else { - durationCode = generator.valueToCode(durationInput, "DURATION", generator.ORDER_ATOMIC); - } - } catch (error) { - console.warn("生成持续时间代码时出错:", error); - durationCode = "1000"; - } - } else { - durationCode = "1000"; - } - - const useBlocking = hasPlayWaitBefore(_block); - const methodName = useBlocking ? 'play_frequency_blocking' : 'play_frequency'; - - return `sound.${methodName}(${frequencyCode}, ${durationCode})\n`; + return `sound.play_frequency(${frequencyInput}, ${durationInput})\n`; }; diff --git a/boards/default_src/python_pyodide/generators/sound/play/play_frequency_no_duration.js b/boards/default_src/python_pyodide/generators/sound/play/play_frequency_no_duration.js index 4c47f1c1..a1b6c18a 100644 --- a/boards/default_src/python_pyodide/generators/sound/play/play_frequency_no_duration.js +++ b/boards/default_src/python_pyodide/generators/sound/play/play_frequency_no_duration.js @@ -1,58 +1,9 @@ -function hasPlayWaitBefore(block) { - let currentBlock = block.getPreviousBlock(); - while (currentBlock) { - if (currentBlock.type === 'sound_play_wait') { - return true; - } - currentBlock = currentBlock.getPreviousBlock(); - } - return false; -} - -export const sound_play_frequency_no_duration = function(_block, generator) { +export const sound_play_frequency_no_duration = function (_block, generator) { if (!generator.definitions_['import_sound']) { generator.definitions_['import_sound'] = 'import sound'; } - const frequencyInput = _block.getInputTargetBlock("FREQUENCY"); - let frequencyCode; + const frequencyInput = generator.valueToCode(this, "FREQUENCY", generator.ORDER_ATOMIC); - if (frequencyInput) { - try { - if (frequencyInput.type === "sound_note") { - const note = frequencyInput.getFieldValue("NOTE") || "NOTE_A4"; - const noteFrequencies = { - "NOTE_B3": 247, - "NOTE_C4": 262, - "NOTE_D4": 294, - "NOTE_E4": 330, - "NOTE_F4": 349, - "NOTE_G4": 392, - "NOTE_A4": 440, - "NOTE_B4": 494, - "NOTE_C5": 523, - "NOTE_D5": 587, - "NOTE_E5": 659, - "NOTE_F5": 698, - "NOTE_G5": 784 - }; - frequencyCode = noteFrequencies[note] || 440; - } else if (frequencyInput.type === "math_number") { - const numValue = frequencyInput.getFieldValue("NUM"); - frequencyCode = numValue || "440"; - } else { - frequencyCode = generator.valueToCode(frequencyInput, "FREQUENCY", generator.ORDER_ATOMIC); - } - } catch (error) { - console.warn("生成频率代码时出错:", error); - frequencyCode = "440"; - } - } else { - frequencyCode = "440"; - } - - const useBlocking = hasPlayWaitBefore(_block); - const methodName = useBlocking ? 'play_frequency_blocking' : 'play_frequency'; - - return `sound.${methodName}(${frequencyCode}, 0)\n`; + return `sound.play_frequency_no_duration(${frequencyInput})\n`; }; diff --git a/boards/default_src/python_pyodide/generators/sound/play/play_note_list.js b/boards/default_src/python_pyodide/generators/sound/play/play_note_list.js index 2336e091..97d56b47 100644 --- a/boards/default_src/python_pyodide/generators/sound/play/play_note_list.js +++ b/boards/default_src/python_pyodide/generators/sound/play/play_note_list.js @@ -1,23 +1,9 @@ -function hasPlayWaitBefore(block) { - let currentBlock = block.getPreviousBlock(); - while (currentBlock) { - if (currentBlock.type === 'sound_play_wait') { - return true; - } - currentBlock = currentBlock.getPreviousBlock(); - } - return false; -} - export const sound_play_note_list = function(_block, _generator) { if (!_generator.definitions_['import_sound']) { _generator.definitions_['import_sound'] = 'import sound'; } - const noteList = _block.getFieldValue("NOTE_LIST") || "DADADADUM"; + const noteList = this.getFieldValue("NOTE_LIST"); - const useBlocking = hasPlayWaitBefore(_block); - const methodName = useBlocking ? 'play_note_list_blocking' : 'play_note_list'; - - return `sound.${methodName}("${noteList}")\n`; + return `sound.play_note_list("${noteList}")\n`; }; \ No newline at end of file diff --git a/boards/default_src/python_pyodide/generators/sound/play/sound_note.js b/boards/default_src/python_pyodide/generators/sound/play/sound_note.js index 4241293f..8f243c8d 100644 --- a/boards/default_src/python_pyodide/generators/sound/play/sound_note.js +++ b/boards/default_src/python_pyodide/generators/sound/play/sound_note.js @@ -1,6 +1,7 @@ -export const sound_note = function(_block, generator) { - const note = _block.getFieldValue("NOTE") || "NOTE_A4"; - return [`"${note}"`, generator.ORDER_ATOMIC]; +export const sound_note = function (_block, generator) { + // 获取频率值(字符串格式) + const frequency = this.getFieldValue("NOTE"); + return [frequency, generator.ORDER_ATOMIC]; }; diff --git a/boards/default_src/python_pyodide/index.js b/boards/default_src/python_pyodide/index.js index 02c85a14..6d0406cc 100644 --- a/boards/default_src/python_pyodide/index.js +++ b/boards/default_src/python_pyodide/index.js @@ -156,79 +156,58 @@ Object.assign( import { sound_play } from './blocks/sound/play/play.js'; import { sound_play_wait } from './blocks/sound/play/play_wait.js'; import { sound_stop_all } from './blocks/sound/play/sound_stop_all.js'; - import { sound_effect_add } from './blocks/sound/effect/add.js'; import { sound_effect_set } from './blocks/sound/effect/sound_effect_set.js'; import { sound_effect_clear } from './blocks/sound/effect/sound_effect_clear.js'; - import { sound_volume_add } from './blocks/sound/volume/add.js'; import { sound_volume_set } from './blocks/sound/volume/set.js'; import { sound_volume_get } from './blocks/sound/volume/get.js'; - import { sound_record } from './blocks/sound/play/record.js'; - import { sound_play_frequency } from './blocks/sound/play/play_frequency.js'; import { sound_play_frequency_no_duration } from './blocks/sound/play/play_frequency_no_duration.js'; import { sound_play_note_list } from './blocks/sound/play/play_note_list.js'; import { sound_note } from './blocks/sound/play/sound_note.js'; - import { sound_play as sound_play_gen } from './generators/sound/play/play.js'; import { sound_play_wait as sound_play_wait_gen } from './generators/sound/play/play_wait.js'; import { sound_stop_all as sound_stop_all_gen } from './generators/sound/play/sound_stop_all.js'; - import { sound_effect_add as sound_effect_add_gen } from './generators/sound/effect/add.js'; import { sound_effect_set as sound_effect_set_gen } from './generators/sound/effect/sound_effect_set.js'; import { sound_effect_clear as sound_effect_clear_gen } from './generators/sound/effect/sound_effect_clear.js'; - import { sound_volume_add as sound_volume_add_gen } from './generators/sound/volume/add.js'; import { sound_volume_set as sound_volume_set_gen } from './generators/sound/volume/set.js'; import { sound_volume_get as sound_volume_get_gen } from './generators/sound/volume/get.js'; - import { sound_record as sound_record_gen } from './generators/sound/play/record.js'; - import { sound_play_frequency as sound_play_frequency_gen } from './generators/sound/play/play_frequency.js'; import { sound_play_frequency_no_duration as sound_play_frequency_no_duration_gen } from './generators/sound/play/play_frequency_no_duration.js'; import { sound_play_note_list as sound_play_note_list_gen } from './generators/sound/play/play_note_list.js'; import { sound_note as sound_note_gen } from './generators/sound/play/sound_note.js'; - -Object.assign(Blockly.Blocks, { - sound_play, - sound_play_wait, - sound_stop_all, - sound_effect_add, - sound_effect_set, - sound_effect_clear, - sound_volume_add, - sound_volume_set, - sound_volume_get, +const soundBlocks = { + sound_play, sound_play_wait, sound_stop_all, + sound_effect_add, sound_effect_set, sound_effect_clear, + sound_volume_add, sound_volume_set, sound_volume_get, sound_record, + sound_play_frequency, sound_play_frequency_no_duration, + sound_play_note_list, sound_note +}; - sound_play_frequency, - sound_play_frequency_no_duration, - sound_play_note_list, - sound_note, -}); - - -Object.assign(Blockly.Python.forBlock, { +const soundGenerators = { sound_play: sound_play_gen, sound_play_wait: sound_play_wait_gen, sound_stop_all: sound_stop_all_gen, - sound_effect_add: sound_effect_add_gen, sound_effect_set: sound_effect_set_gen, sound_effect_clear: sound_effect_clear_gen, - sound_volume_add: sound_volume_add_gen, sound_volume_set: sound_volume_set_gen, sound_volume_get: sound_volume_get_gen, - sound_record: sound_record_gen, - sound_play_frequency: sound_play_frequency_gen, sound_play_frequency_no_duration: sound_play_frequency_no_duration_gen, sound_play_note_list: sound_play_note_list_gen, - sound_note: sound_note_gen, -}); + sound_note: sound_note_gen +}; + +Object.assign(Blockly.Blocks, soundBlocks); +Object.assign(Blockly.Python.forBlock, soundGenerators); window.sound = sound; \ No newline at end of file diff --git a/boards/default_src/python_pyodide/others/sound.js b/boards/default_src/python_pyodide/others/sound.js index 436b0e1d..7e79c88d 100644 --- a/boards/default_src/python_pyodide/others/sound.js +++ b/boards/default_src/python_pyodide/others/sound.js @@ -632,6 +632,7 @@ const sound = { "NOTE_G5": 784 }, + // 播放指定频率的声音(带持续时间) play_frequency: (frequency, duration = 1000) => { try { sound.initAudioContext(); @@ -657,31 +658,84 @@ const sound = { gainNode.connect(sound.audioContext.destination); oscillator.start(); - - if (duration > 0) { - oscillator.stop(sound.audioContext.currentTime + duration / 1000); - } else { - oscillator.stop(sound.audioContext.currentTime + 2); - } + oscillator.stop(sound.audioContext.currentTime + duration / 1000); + console.log(`播放频率: ${frequency}Hz, 持续时间: ${duration}ms, 音量: ${sound.volume}%`); } } catch (error) { console.error("播放频率声音失败:", error); } }, - play_frequency_blocking: (frequency, duration = 1000) => { - return new Promise((resolve, reject) => { - const actualDuration = duration > 0 ? duration : 2000; + // 播放指定频率的声音(持续播放,无持续时间限制) + play_frequency_continuous: (frequency) => { + try { + sound.initAudioContext(); + if (sound.audioContext) { + const oscillator = sound.audioContext.createOscillator(); + const gainNode = sound.audioContext.createGain(); + + oscillator.frequency.setValueAtTime(frequency, sound.audioContext.currentTime); + oscillator.type = 'sine'; + + const currentPitch = sound.effects.pitch; + if (currentPitch !== 0) { + oscillator.frequency.setValueAtTime( + frequency * Math.pow(2, currentPitch / 12), + sound.audioContext.currentTime + ); + } + + gainNode.gain.setValueAtTime(sound.volume / 100, sound.audioContext.currentTime); + + oscillator.connect(gainNode); + gainNode.connect(sound.audioContext.destination); + + oscillator.start(); + oscillator.stop(sound.audioContext.currentTime + 2); // 默认播放2秒 + console.log(`播放频率(持续): ${frequency}Hz, 无持续时间限制, 音量: ${sound.volume}%`); + } + } catch (error) { + console.error("播放频率声音失败:", error); + } + }, + + // 播放指定频率的声音(阻塞版本,使用队列,带持续时间) + play_frequency_blocking: (frequency, duration = 1000) => { + console.log(`=== 🔒 阻塞播放频率(加入队列): ${frequency}Hz, ${duration}ms ===`); + + return new Promise((resolve, reject) => { const queueItem = { type: 'frequency', frequency, - duration: actualDuration, + duration: duration, resolve, reject }; sound.soundQueue.push(queueItem); + console.log(`✅ 频率已加入声音队列,当前队列长度: ${sound.soundQueue.length}`); + sound.processQueue(); + }); + }, + + // 播放指定频率的声音(阻塞版本,使用队列,持续播放) + play_frequency_continuous_blocking: (frequency) => { + console.log(`=== 🔒 阻塞播放频率(持续,加入队列): ${frequency}Hz ===`); + + return new Promise((resolve, reject) => { + const duration = 2000; // 持续播放默认2秒 + + const queueItem = { + type: 'frequency', + frequency, + duration: duration, + resolve, + reject + }; + + sound.soundQueue.push(queueItem); + console.log(`✅ 频率(持续)已加入声音队列,当前队列长度: ${sound.soundQueue.length}`); sound.processQueue(); }); }, @@ -1386,11 +1440,33 @@ function injectSoundToPython() { return sound.clear_effects(); }, play_frequency: (frequency, duration) => { + console.log(`Python调用: sound.play_frequency(${frequency}, ${duration})`); + // 自动判断是否需要阻塞 + if (sound.soundQueue.length > 0 || sound.isProcessingQueue) { + return sound.play_frequency_blocking(frequency, duration); + } return sound.play_frequency(frequency, duration); }, + play_frequency_no_duration: (frequency) => { + console.log(`Python调用: sound.play_frequency_no_duration(${frequency})`); + // 自动判断是否需要阻塞 + if (sound.soundQueue.length > 0 || sound.isProcessingQueue) { + return sound.play_frequency_continuous_blocking(frequency); + } + return sound.play_frequency_continuous(frequency); + }, play_frequency_blocking: (frequency, duration) => { + console.log(`Python调用: sound.play_frequency_blocking(${frequency}, ${duration})`); return sound.play_frequency_blocking(frequency, duration); }, + play_frequency_continuous: (frequency) => { + console.log(`Python调用: sound.play_frequency_continuous(${frequency})`); + return sound.play_frequency_continuous(frequency); + }, + play_frequency_continuous_blocking: (frequency) => { + console.log(`Python调用: sound.play_frequency_continuous_blocking(${frequency})`); + return sound.play_frequency_continuous_blocking(frequency); + }, play_note_list: (noteList) => { return sound.play_note_list(noteList); }, @@ -1463,6 +1539,100 @@ def _sync_play_blocking(name): # 替换sound.play_blocking为同步版本 sound.play_blocking = _sync_play_blocking +# 创建同步版本的play_frequency包装函数 +_original_play_frequency = sound.play_frequency + +def _sync_play_frequency(frequency, duration): + """同步版本的play_frequency,会自动判断是否需要阻塞""" + promise = _original_play_frequency(frequency, duration) + # 使用Pyodide的Promise支持 + import pyodide + if hasattr(pyodide, 'ffi') and hasattr(pyodide.ffi, 'run_sync'): + try: + return pyodide.ffi.run_sync(promise) + except Exception: + return None + else: + import asyncio + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def wait_promise(): + try: + return await promise + except Exception: + return None + + try: + return loop.run_until_complete(wait_promise()) + except Exception: + return None + +# 创建同步版本的play_frequency_no_duration包装函数 +_original_play_frequency_no_duration = sound.play_frequency_no_duration + +def _sync_play_frequency_no_duration(frequency): + """同步版本的play_frequency_no_duration,会自动判断是否需要阻塞""" + promise = _original_play_frequency_no_duration(frequency) + # 使用Pyodide的Promise支持 + import pyodide + if hasattr(pyodide, 'ffi') and hasattr(pyodide.ffi, 'run_sync'): + try: + return pyodide.ffi.run_sync(promise) + except Exception: + return None + else: + import asyncio + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def wait_promise(): + try: + return await promise + except Exception: + return None + + try: + return loop.run_until_complete(wait_promise()) + except Exception: + return None + +# 替换为同步版本 +sound.play_frequency = _sync_play_frequency +sound.play_frequency_no_duration = _sync_play_frequency_no_duration + +# 创建同步版本的play_frequency_continuous_blocking包装函数 +_original_play_frequency_continuous_blocking = sound.play_frequency_continuous_blocking + +def _sync_play_frequency_continuous_blocking(frequency): + """同步版本的play_frequency_continuous_blocking,会等待频率播放完成""" + promise = _original_play_frequency_continuous_blocking(frequency) + # 使用Pyodide的Promise支持 + import pyodide + if hasattr(pyodide, 'ffi') and hasattr(pyodide.ffi, 'run_sync'): + return pyodide.ffi.run_sync(promise) + else: + import asyncio + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def wait_promise(): + return await promise + + return loop.run_until_complete(wait_promise()) + +# 替换为同步版本 +sound.play_frequency_continuous_blocking = _sync_play_frequency_continuous_blocking + # 创建同步版本的play_frequency_blocking包装函数 _original_play_frequency_blocking = sound.play_frequency_blocking diff --git a/boards/default_src/python_pyodide/template.xml b/boards/default_src/python_pyodide/template.xml index 8f0660be..06e62cd6 100644 --- a/boards/default_src/python_pyodide/template.xml +++ b/boards/default_src/python_pyodide/template.xml @@ -3846,20 +3846,7 @@ - - - - - - - - - - - - - - + @@ -3916,6 +3903,20 @@ NOTE_A4 - + + + + + + + + + + + + + + + \ No newline at end of file