当前位置: 首页 > article >正文

JavaScript系列(47)--音频处理系统详解

JavaScript音频处理系统详解 🎵

今天,让我们深入探讨JavaScript的音频处理系统。Web Audio API为我们提供了强大的音频处理和合成能力,让我们能够在浏览器中实现复杂的音频应用。

音频系统基础概念 🌟

💡 小知识:Web Audio API使用音频上下文(AudioContext)作为处理音频的核心,它提供了一个模块化的音频处理图(Audio Graph)系统,通过连接不同的音频节点来处理和生成声音。

基本实现 📊

// 1. 音频上下文管理
class AudioContextManager {constructor() {this.context = new (window.AudioContext || window.webkitAudioContext)();this.masterGain = this.context.createGain();this.masterGain.connect(this.context.destination);}// 恢复音频上下文async resume() {if (this.context.state === 'suspended') {await this.context.resume();}}// 暂停音频上下文async suspend() {if (this.context.state === 'running') {await this.context.suspend();}}// 获取当前时间getCurrentTime() {return this.context.currentTime;}// 设置主音量setMasterVolume(value) {this.masterGain.gain.value = Math.max(0, Math.min(1, value));}
}// 2. 音频加载器
class AudioLoader {constructor(context) {this.context = context;this.cache = new Map();}// 加载音频文件async loadAudio(url) {if (this.cache.has(url)) {return this.cache.get(url);}const response = await fetch(url);const arrayBuffer = await response.arrayBuffer();const audioBuffer = await this.context.decodeAudioData(arrayBuffer);this.cache.set(url, audioBuffer);return audioBuffer;}// 预加载多个音频文件async preloadAudios(urls) {return Promise.all(urls.map(url => this.loadAudio(url)));}// 清除缓存clearCache() {this.cache.clear();}
}// 3. 音频播放器
class AudioPlayer {constructor(context) {this.context = context;this.sources = new Map();}// 播放音频play(buffer, options = {}) {const source = this.context.createBufferSource();source.buffer = buffer;const gainNode = this.context.createGain();source.connect(gainNode);gainNode.connect(this.context.destination);// 设置音量gainNode.gain.value = options.volume || 1;// 设置循环if (options.loop) {source.loop = true;if (options.loopStart) source.loopStart = options.loopStart;if (options.loopEnd) source.loopEnd = options.loopEnd;}// 设置播放速率if (options.playbackRate) {source.playbackRate.value = options.playbackRate;}const startTime = options.startTime || 0;source.start(this.context.currentTime, startTime);const id = Date.now().toString();this.sources.set(id, { source, gainNode });return id;}// 停止播放stop(id) {const audio = this.sources.get(id);if (audio) {audio.source.stop();audio.source.disconnect();audio.gainNode.disconnect();this.sources.delete(id);}}// 暂停播放pause(id) {const audio = this.sources.get(id);if (audio) {this.context.suspend();}}// 恢复播放resume(id) {const audio = this.sources.get(id);if (audio) {this.context.resume();}}
}

高级功能实现 🚀

// 1. 音频效果处理器
class AudioEffectProcessor {constructor(context) {this.context = context;}// 创建均衡器createEqualizer() {const bands = [{ frequency: 60, type: 'lowshelf' },{ frequency: 170, type: 'peaking' },{ frequency: 350, type: 'peaking' },{ frequency: 1000, type: 'peaking' },{ frequency: 3500, type: 'peaking' },{ frequency: 10000, type: 'highshelf' }];const filters = bands.map(band => {const filter = this.context.createBiquadFilter();filter.type = band.type;filter.frequency.value = band.frequency;filter.gain.value = 0;filter.Q.value = 1;return filter;});// 连接滤波器for (let i = 0; i < filters.length - 1; i++) {filters[i].connect(filters[i + 1]);}return {input: filters[0],output: filters[filters.length - 1],bands: filters};}// 创建压缩器createCompressor() {const compressor = this.context.createDynamicsCompressor();compressor.threshold.value = -24;compressor.knee.value = 30;compressor.ratio.value = 12;compressor.attack.value = 0.003;compressor.release.value = 0.25;return compressor;}// 创建混响效果createReverb(duration = 2) {const sampleRate = this.context.sampleRate;const length = sampleRate * duration;const impulse = this.context.createBuffer(2, length, sampleRate);for (let channel = 0; channel < 2; channel++) {const channelData = impulse.getChannelData(channel);for (let i = 0; i < length; i++) {channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, 2);}}const convolver = this.context.createConvolver();convolver.buffer = impulse;return convolver;}
}// 2. 音频分析器
class AudioAnalyzer {constructor(context) {this.context = context;this.analyzer = context.createAnalyser();this.analyzer.fftSize = 2048;this.bufferLength = this.analyzer.frequencyBinCount;this.dataArray = new Uint8Array(this.bufferLength);}// 获取频率数据getFrequencyData() {this.analyzer.getByteFrequencyData(this.dataArray);return this.dataArray;}// 获取波形数据getWaveformData() {this.analyzer.getByteTimeDomainData(this.dataArray);return this.dataArray;}// 计算音量级别getVolume() {const frequencyData = this.getFrequencyData();const average = frequencyData.reduce((a, b) => a + b) / frequencyData.length;return average / 255; // 归一化到0-1范围}// 检测节拍detectBeat(threshold = 0.8) {const volume = this.getVolume();return volume > threshold;}
}// 3. 音频合成器
class AudioSynthesizer {constructor(context) {this.context = context;}// 创建振荡器createOscillator(options = {}) {const oscillator = this.context.createOscillator();oscillator.type = options.type || 'sine';oscillator.frequency.value = options.frequency || 440;const gainNode = this.context.createGain();gainNode.gain.value = options.gain || 0.5;oscillator.connect(gainNode);return { oscillator, gainNode };}// 创建包络createEnvelope(gainNode, options = {}) {const now = this.context.currentTime;const gain = gainNode.gain;gain.cancelScheduledValues(now);gain.setValueAtTime(0, now);gain.linearRampToValueAtTime(1, now + (options.attack || 0.1));gain.linearRampToValueAtTime(options.sustain || 0.5, now + (options.decay || 0.2));gain.linearRampToValueAtTime(0, now + (options.release || 0.5));}// 创建噪声发生器createNoiseGenerator() {const bufferSize = 2 * this.context.sampleRate;const noiseBuffer = this.context.createBuffer(1, bufferSize, this.context.sampleRate);const output = noiseBuffer.getChannelData(0);for (let i = 0; i < bufferSize; i++) {output[i] = Math.random() * 2 - 1;}const noise = this.context.createBufferSource();noise.buffer = noiseBuffer;noise.loop = true;return noise;}
}

实际应用场景 💼

// 1. 音乐播放器实现
class MusicPlayer {constructor() {this.audioManager = new AudioContextManager();this.loader = new AudioLoader(this.audioManager.context);this.player = new AudioPlayer(this.audioManager.context);this.effects = new AudioEffectProcessor(this.audioManager.context);this.analyzer = new AudioAnalyzer(this.audioManager.context);this.playlist = [];this.currentTrack = null;}// 添加音轨async addTrack(url) {const buffer = await this.loader.loadAudio(url);this.playlist.push({ url, buffer });}// 播放音轨playTrack(index) {if (this.currentTrack) {this.player.stop(this.currentTrack);}const track = this.playlist[index];if (track) {this.currentTrack = this.player.play(track.buffer, {volume: 0.8,loop: false});}}// 设置均衡器setEqualizer(bands) {const equalizer = this.effects.createEqualizer();bands.forEach((gain, index) => {equalizer.bands[index].gain.value = gain;});}
}// 2. 音效系统实现
class SoundEffectSystem {constructor() {this.audioManager = new AudioContextManager();this.loader = new AudioLoader(this.audioManager.context);this.effects = new Map();}// 加载音效async loadEffect(name, url) {const buffer = await this.loader.loadAudio(url);this.effects.set(name, buffer);}// 播放音效playEffect(name, options = {}) {const buffer = this.effects.get(name);if (buffer) {const source = this.audioManager.context.createBufferSource();source.buffer = buffer;const gainNode = this.audioManager.context.createGain();gainNode.gain.value = options.volume || 1;source.connect(gainNode);gainNode.connect(this.audioManager.masterGain);source.start();}}
}// 3. 音频可视化实现
class AudioVisualizer {constructor(canvas, audioContext) {this.canvas = canvas;this.context = canvas.getContext('2d');this.analyzer = new AudioAnalyzer(audioContext);this.isRunning = false;}// 开始可视化start() {this.isRunning = true;this.draw();}// 停止可视化stop() {this.isRunning = false;}// 绘制频谱draw() {if (!this.isRunning) return;const width = this.canvas.width;const height = this.canvas.height;this.context.clearRect(0, 0, width, height);const frequencyData = this.analyzer.getFrequencyData();const barWidth = width / frequencyData.length;this.context.fillStyle = '#00ff00';for (let i = 0; i < frequencyData.length; i++) {const barHeight = (frequencyData[i] / 255) * height;const x = i * barWidth;const y = height - barHeight;this.context.fillRect(x, y, barWidth - 1, barHeight);}requestAnimationFrame(() => this.draw());}
}

性能优化技巧 ⚡

// 1. 音频缓冲区管理
class AudioBufferPool {constructor(context, maxSize = 10) {this.context = context;this.maxSize = maxSize;this.pool = new Map();}// 获取缓冲区acquire(size) {const key = size.toString();if (!this.pool.has(key)) {this.pool.set(key, []);}const buffers = this.pool.get(key);if (buffers.length > 0) {return buffers.pop();}return this.context.createBuffer(2, size, this.context.sampleRate);}// 释放缓冲区release(buffer) {const key = buffer.length.toString();if (!this.pool.has(key)) {this.pool.set(key, []);}const buffers = this.pool.get(key);if (buffers.length < this.maxSize) {buffers.push(buffer);}}
}// 2. 音频处理工作线程
class AudioWorkerProcessor {constructor() {this.worker = new Worker('audio-worker.js');this.callbacks = new Map();}// 发送处理任务process(audioData, options) {return new Promise((resolve, reject) => {const id = Date.now().toString();this.callbacks.set(id, { resolve, reject });this.worker.postMessage({id,audioData,options});});}// 初始化工作线程initialize() {this.worker.onmessage = (e) => {const { id, result, error } = e.data;const callback = this.callbacks.get(id);if (callback) {if (error) {callback.reject(error);} else {callback.resolve(result);}this.callbacks.delete(id);}};}
}// 3. 音频流处理优化
class AudioStreamProcessor {constructor(context) {this.context = context;this.processor = this.context.createScriptProcessor(4096, 1, 1);this.isProcessing = false;}// 开始处理start(processCallback) {this.isProcessing = true;this.processor.onaudioprocess = (e) => {if (!this.isProcessing) return;const inputBuffer = e.inputBuffer;const outputBuffer = e.outputBuffer;const inputData = inputBuffer.getChannelData(0);const outputData = outputBuffer.getChannelData(0);// 使用TypedArray提高性能const data = new Float32Array(inputData);const result = processCallback(data);outputData.set(result);};}// 停止处理stop() {this.isProcessing = false;this.processor.onaudioprocess = null;}
}

最佳实践建议 💡

  1. 音频资源管理
// 1. 音频资源预加载
class AudioResourceManager {constructor() {this.resources = new Map();this.loading = new Set();}// 预加载资源async preload(resources) {const loader = new AudioLoader(audioContext);for (const [name, url] of Object.entries(resources)) {if (!this.resources.has(name) && !this.loading.has(url)) {this.loading.add(url);try {const buffer = await loader.loadAudio(url);this.resources.set(name, buffer);} finally {this.loading.delete(url);}}}}// 获取资源get(name) {return this.resources.get(name);}
}// 2. 音频解码优化
class AudioDecoder {constructor(context) {this.context = context;this.decodingQueue = [];this.isDecoding = false;}// 添加解码任务async decode(arrayBuffer) {return new Promise((resolve, reject) => {this.decodingQueue.push({arrayBuffer,resolve,reject});if (!this.isDecoding) {this.processQueue();}});}// 处理解码队列async processQueue() {if (this.decodingQueue.length === 0) {this.isDecoding = false;return;}this.isDecoding = true;const task = this.decodingQueue.shift();try {const audioBuffer = await this.context.decodeAudioData(task.arrayBuffer);task.resolve(audioBuffer);} catch (error) {task.reject(error);}this.processQueue();}
}// 3. 音频状态管理
class AudioStateManager {constructor() {this.states = new Map();this.listeners = new Set();}// 更新状态setState(key, value) {this.states.set(key, value);this.notifyListeners();}// 获取状态getState(key) {return this.states.get(key);}// 添加监听器addListener(listener) {this.listeners.add(listener);}// 移除监听器removeListener(listener) {this.listeners.delete(listener);}// 通知监听器notifyListeners() {for (const listener of this.listeners) {listener(this.states);}}
}

结语 📝

JavaScript的音频处理系统提供了强大的功能,让我们能够在Web应用中实现复杂的音频处理和音效系统。通过本文,我们学习了:

  1. 音频系统的基本概念和实现
  2. 高级音频处理功能
  3. 实际应用场景和示例
  4. 性能优化技巧
  5. 最佳实践和设计模式

💡 学习建议:在使用Web Audio API时,要注意浏览器兼容性和性能优化。对于复杂的音频处理,可以考虑使用Web Worker来避免阻塞主线程。同时,要合理管理音频资源,避免内存泄漏。


如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

JavaScript系列(47)--音频处理系统详解

JavaScript音频处理系统详解 &#x1f3b5; 今天&#xff0c;让我们深入探讨JavaScript的音频处理系统。Web Audio API为我们提供了强大的音频处理和合成能力&#xff0c;让我们能够在浏览器中实现复杂的音频应用。 音频系统基础概念 &#x1f31f; &#x1f4a1; 小知识&…...

【解决方案】VMware虚拟机adb连接宿主机夜神模拟器

1、本机&#xff08;宿主机&#xff0c;系统windows10&#xff09;ip为192.168.31.108 2、运行模拟器后本机cmd查看端口为62026 3、VMware虚拟机&#xff08;系统&#xff0c;kali&#xff09;adb连接192.168.31.108:62026报错 failed to connect to 192.168.31.108:16416: Co…...

DroneXtract:一款针对无人机的网络安全数字取证工具

关于DroneXtract DroneXtract是一款使用 Golang 开发的适用于DJI无人机的综合数字取证套件&#xff0c;该工具可用于分析无人机传感器值和遥测数据、可视化无人机飞行地图、审计威胁活动以及提取多种文件格式中的相关数据。 功能介绍 DroneXtract 具有四个用于无人机取证和审…...

基于springboot+vue的流浪动物救助系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

利用ue5制作CG动画笔记

tips&#xff1a; 按住鼠标中键可以拖动枢轴点 在曲线编辑器中按住shift可以使曲线编辑保持在x轴 专业术语&#xff1a; CGI&#xff1a;计算机生成图象&#xff08;computer-generated imagery&#xff09;真实的不算&#xff0c;计算机生成的 Compositing&#xff1a;合…...

AI 图片涌入百度图库

在这个信息爆炸的时代&#xff0c;我们习惯了通过搜索引擎来获取各种想要的信息和图片。然而&#xff0c;现在打开搜索引擎看到的却是许多真假难辨的信息——AI图片&#xff0c;这部分数据正以惊人的速度涌入百度图库&#xff0c;让小编不禁想问&#xff1a;未来打开百度图库不…...

《多阶段渐进式图像修复》学习笔记

paper&#xff1a;2102.02808 GitHub&#xff1a;swz30/MPRNet: [CVPR 2021] Multi-Stage Progressive Image Restoration. SOTA results for Image deblurring, deraining, and denoising. 目录 摘要 1、介绍 2、相关工作 2.1 单阶段方法 2.2 多阶段方法 2.3 注意力机…...

uniapp使用uni.navigateBack返回页面时携带参数到上个页面

我们平时开发中也经常遇到这种场景&#xff0c;跳转一个页面会进行一些操作&#xff0c;操作完成后再返回上个页面同时要携带着一些参数 其实也很简单&#xff0c;也来记录一下吧 假设从A页面 跳转到 B页面 A页面 直接上完整代码了哈&#xff0c;很简单&#xff1a; <t…...

2025.1.26机器学习笔记:C-RNN-GAN文献阅读

2025.1.26周报 文献阅读题目信息摘要Abstract创新点网络架构实验结论缺点以及后续展望 总结 文献阅读 题目信息 题目&#xff1a; C-RNN-GAN: Continuous recurrent neural networks with adversarial training会议期刊&#xff1a; NIPS作者&#xff1a; Olof Mogren发表时间…...

goframe 多语言国际化解决方案

项目背景 本项目采用基于JSON配置的多语言国际化&#xff08;i18n&#xff09;解决方案&#xff0c;支持多种语言的无缝切换和本地化。 目录结构 manifest/ └── i18n/├── zh.json # 简体中文├── zh-tw.json # 繁体中文├── en.json # 英语├…...

Deepseek R1 的大模拟考试

本文章同步发布于洛谷专栏。 前情提要&#xff1a;联网&#xff0c;R1。 Summary P4896 OIer们的烦恼&#xff1a;WA 30pts。P1580 yyy loves Easter_Egg I&#xff1a;WA 0pts。P5006 [yLOI2018] 大美江湖&#xff1a;AC。P2830 写程序&#xff1a;WA 33pts。 总 AC 题数&…...

机器人介绍

以下是关于机器人的介绍&#xff1a; 定义 机器人是一种能够自动执行任务的机器系统&#xff0c;它集成了机电、机构学、材料学及仿生学等多个学科技术&#xff0c;可以接受人类指挥&#xff0c;运行预先编排的程序&#xff0c;或根据人工智能技术制定的原则纲领行动&#xf…...

设置jmeter界面图标字体大小

设置jmeter界面图标字体大小 方法&#xff1a;点击“选项” -> 点击放大、缩小。&#xff08;可进行全局的菜单、左侧目录结构树、元件界面显示等字体图标的放大、缩小。&#xff09;...

JavaScript逆向高阶指南:突破基础,掌握核心逆向技术

JavaScript逆向高阶指南&#xff1a;突破基础&#xff0c;掌握核心逆向技术 JavaScript逆向工程是Web开发者和安全分析师的核心竞争力。无论是解析混淆代码、分析压缩脚本&#xff0c;还是逆向Web应用架构&#xff0c;掌握高阶逆向技术都将助您深入理解复杂JavaScript逻辑。本…...

使用 MSYS2 qemu 尝鲜Arm64架构国产Linux系统

近期&#xff0c;我的师弟咨询我关于Arm64架构的国产CPU国产OS开发工具链问题。他们公司因为接手了一个国企的单子&#xff0c;需要在这类环境下开发程序。说实在的我也没有用过这个平台&#xff0c;但是基于常识&#xff0c;推测只要基于C和Qt&#xff0c;应该问题不大。 1. …...

RocketMQ实战—1.订单系统面临的技术挑战

大纲 1.一个订单系统的整体架构、业务流程及负载情况 2.订单系统面临的技术问题一&#xff1a;下订单的同时还要发券、发红包、Push推送等导致性能太差 3.订单系统面临的技术问题二&#xff1a;订单退款时经常流程失败导致无法完成退款 4.订单系统面临的技术问题三&#xf…...

【QT】- QUdpSocket

QUdpSocket 是 Qt 自带的一个类&#xff0c;属于 Qt 网络模块&#xff0c;用于进行 UDP&#xff08;用户数据报协议&#xff09; 通信。它提供了简便的接口来发送和接收 UDP 数据报&#xff08;datagrams&#xff09;。 UDP 是一种无连接的协议&#xff0c;适用于那些不需要确…...

赚钱的究极认识

1、赚钱的本质是提供了价值或者价值想象 价值&#xff1a; 比如小米手机靠什么&#xff1f;“性价比”&#xff0c;什么饥饿营销&#xff0c;创新&#xff0c;用户参与&#xff0c;生态供应链&#xff0c;品牌这些不能说不重要&#xff0c;但是加在一起都没有“性价比”这3字重…...

Linux学习笔记——用户管理

一、用户管理命令 useradd #用户增加命令 usermod #用户修改命令 passwd #密码修改命令 userdel #用户删除命令 su #用户提权命令 1、useradd命令&#xff08;加用户&#xff09;&#xff1a; 创建并设置用户信息&#xff0c;使用us…...

【AI】【本地部署】OpenWebUI的升级并移植旧有用户信息

【背景】 OpenWebUI的版本升级频率很高&#xff0c;并会修改旧版本的Bug&#xff0c;不过对于已经在使用的系统&#xff0c;升级后现有用户信息都会丢失&#xff0c;于是研究如何在升级后将现有的用户信息移植到升级后版本。 【准备工作】 OpenWebUI的升级步骤在Docker中有现…...

从synchronized到ReentrantLock_Java锁机制的演进与选择

1 引言 在Java并发编程中,锁机制是确保线程安全的关键。synchronized关键字和显式锁(如ReentrantLock)是两种常用的锁机制。本文将深入探讨这两种锁的工作原理、优缺点,并分析它们在不同场景下的最佳选择,帮助开发者做出明智的选择。 2 synchronized关键字详解 synchro…...

PyCharm接入DeepSeek实现AI编程

目录 效果演示 创建API key 在PyCharm中下载CodeGPT插件 配置Continue DeepSeek 是一家专注于人工智能技术研发的公司&#xff0c;致力于开发高性能、低成本的 AI 模型。DeepSeek-V3 是 DeepSeek 公司推出的最新一代 AI 模型。其前身是 DeepSeek-V2.5&#xff0c;经过持续的…...

21款炫酷烟花合集

系列专栏 《Python趣味编程》《C/C趣味编程》《HTML趣味编程》《Java趣味编程》 写在前面 Python、C/C、HTML、Java等4种语言实现18款炫酷烟花的代码。 Python Python烟花① 完整代码&#xff1a;Python动漫烟花&#xff08;完整代码&#xff09; ​ Python烟花② 完整…...

数论问题75

命题&#xff0c;证明:存在K∈N&#xff0c;使得对于每个n∈N&#xff0c;Kx2^n1都是合数。 证明:设n2^m&#xff0c;当m0&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;4时&#xff0c;a(m)2^(2^m)1都是素数。 a(0)213&#xff0c;a(1)2^215&#xff0c;a(2)2^4117&…...

zyNo.15(Web题型总结1)

web 一、工具使用 1.sqlmap使用 在目录页输入cmd就可以打开程序 使用方法查看输入python sqlmap.py --help 二、web攻防知识体系 新手村 WEB CTF入门 md5绕过、变量覆盖、随机数问题 sql注入 MySQL注入介绍与联合…...

将 OneLake 数据索引到 Elasticsearch - 第 1 部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 学习配置 OneLake&#xff0c;使用 Python 消费数据并在 Elasticsearch 中索引文档&#xff0c;然后运行语义搜索。 OneLake 是一款工具&#xff0c;可让你连接到不同的 Microsoft 数据源&#xff0c;例如 Power BI、Data Activ…...

Spring Boot多环境配置实践指南

在开发Spring Boot应用时&#xff0c;我们常常需要根据不同的运行环境&#xff08;如开发环境、测试环境和生产环境&#xff09;来配置不同的参数。Spring Boot提供了非常灵活的多环境配置机制&#xff0c;通过使用profile-specific properties文件&#xff0c;我们可以轻松地管…...

C++11中array容器的常见用法

文章目录 一、概述二、std::array的特点三、std::array的定义与初始化三、std::array的常用成员函数四、与 C 风格数组的互操作 一、概述 在 C11 中&#xff0c;std::array 是一个新的容器类型&#xff0c;它提供了一个固定大小的数组封装。相比传统的 C 风格数组&#xff0c;…...

NFTs 是网络艺术,而非数字艺术

传统艺术界对 NFTs 的误解 传统艺术界&#xff08;包括博物馆、策展人等&#xff09;常常认为&#xff1a; “我们收藏和策展数字艺术已经数十年了。我们非常了解这个领域。或许少数 NFT 可以被视为优秀的数字艺术&#xff0c;但更多的只是糟糕的数字艺术&#xff0c;甚至根本称…...

澳洲硕士毕业论文写作中如何把握主题

每到毕业季时&#xff0c;澳洲硕士毕业论文写作是留学生学业的头等大事。但是经常有留学生在澳洲毕业论文写作过程中会遇到写了一半&#xff0c;但是不知道应该如何继续下去的问题。有时候是在literature review的部分就越写越觉得偏离了方向&#xff0c;有时候是在数据收集阶段…...